fix(executor): insert the github keys added by the runner at their sorted place
- commit
- 9845c398f5f916bc280ef322622006911d77dbdb
- author
- brice · · 2026-10-12 23:10 UTC
- parent
- e1538eb
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +51 -4
M internal/executor/contexts.go +33 -4
| @@ -77,6 +77,35 @@ func setKey(d *expr.Dictionary, key string, v expr.Value) *expr.Dictionary { | |||
| 77 | 77 | return out | |
| 78 | 78 | } | |
| 79 | 79 | ||
| 80 | + | // setKeySorted is setKey for the github context: a key that is not there yet | |
| 81 | + | // goes before the first key that sorts after it (bytewise; "token" stays first) | |
| 82 | + | // instead of at the end, so that the keys the runner adds (workspace, | |
| 83 | + | // secret_source, the file commands...) sit where the ermite-conformance | |
| 84 | + | // recordings list them. Note that the recordings are normalised with sorted | |
| 85 | + | // keys, so they show the sorted order, not necessarily the insertion order of | |
| 86 | + | // GitHub's runner (SetGitHubContext appends). Keys that exist are replaced in | |
| 87 | + | // place. | |
| 88 | + | func setKeySorted(d *expr.Dictionary, key string, v expr.Value) *expr.Dictionary { | |
| 89 | + | for _, p := range d.Pairs() { | |
| 90 | + | if strings.EqualFold(p.Key, key) { | |
| 91 | + | return setKey(d, key, v) | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | out := expr.NewDictionary() | |
| 95 | + | done := false | |
| 96 | + | for _, p := range d.Pairs() { | |
| 97 | + | if !done && p.Key > key && !strings.EqualFold(p.Key, "token") { | |
| 98 | + | out.Add(key, v) | |
| 99 | + | done = true | |
| 100 | + | } | |
| 101 | + | out.Add(p.Key, p.Value) | |
| 102 | + | } | |
| 103 | + | if !done { | |
| 104 | + | out.Add(key, v) | |
| 105 | + | } | |
| 106 | + | return out | |
| 107 | + | } | |
| 108 | + | ||
| 80 | 109 | func str(d *expr.Dictionary, key string) string { | |
| 81 | 110 | if v, ok := d.Get(key); ok { | |
| 82 | 111 | if s, ok := v.(expr.String); ok { | |
| @@ -222,7 +251,7 @@ func NewState(contextsJSON, matrixJSON, jobKey string, r RunnerInfo, repoFallbac | |||
| 222 | 251 | } | |
| 223 | 252 | } | |
| 224 | 253 | if str(gh, "job") == "" && jobKey != "" { | |
| 225 | - | gh = setKey(gh, "job", expr.String(jobKey)) | |
| 254 | + | gh = setKeySorted(gh, "job", expr.String(jobKey)) | |
| 226 | 255 | } | |
| 227 | 256 | repo := repoName(str(gh, "repository")) | |
| 228 | 257 | if repo == "" { | |
| @@ -233,7 +262,7 @@ func NewState(contextsJSON, matrixJSON, jobKey string, r RunnerInfo, repoFallbac | |||
| 233 | 262 | } | |
| 234 | 263 | home := "/home/runner" | |
| 235 | 264 | s.layout = NewLayout(home, repo, "") | |
| 236 | - | s.github = setKey(gh, "workspace", expr.String(s.layout.Workspace)) | |
| 265 | + | s.github = setKeySorted(gh, "workspace", expr.String(s.layout.Workspace)) | |
| 237 | 266 | return s, s.layout, s.initRest(root, matrixJSON, r) | |
| 238 | 267 | } | |
| 239 | 268 | ||
| @@ -241,7 +270,7 @@ func NewState(contextsJSON, matrixJSON, jobKey string, r RunnerInfo, repoFallbac | |||
| 241 | 270 | // must be called before the first step. | |
| 242 | 271 | func (s *State) WithLayout(l Layout) { | |
| 243 | 272 | s.layout = l | |
| 244 | - | s.github = setKey(s.github, "workspace", expr.String(l.Workspace)) | |
| 273 | + | s.github = setKeySorted(s.github, "workspace", expr.String(l.Workspace)) | |
| 245 | 274 | s.runner = setKey(s.runner, "temp", expr.String(l.Temp)) | |
| 246 | 275 | s.runner = setKey(s.runner, "tool_cache", expr.String(l.ToolCache)) | |
| 247 | 276 | s.runner = setKey(s.runner, "workspace", expr.String(l.RunnerWorkspace)) | |
| @@ -315,7 +344,7 @@ func (s *State) initRest(root *expr.Dictionary, matrixJSON string, r RunnerInfo) | |||
| 315 | 344 | func (s *State) Layout() Layout { return s.layout } | |
| 316 | 345 | ||
| 317 | 346 | // SetGitHub sets a key of the github context (SetGitHubContext). | |
| 318 | - | func (s *State) SetGitHub(k, v string) { s.github = setKey(s.github, k, expr.String(v)) } | |
| 347 | + | func (s *State) SetGitHub(k, v string) { s.github = setKeySorted(s.github, k, expr.String(v)) } | |
| 319 | 348 | ||
| 320 | 349 | // GitHub returns a string key of the github context ("" when absent). | |
| 321 | 350 | func (s *State) GitHub(k string) string { return str(s.github, k) } | |
| @@ -77,6 +77,35 @@ func setKey(d *expr.Dictionary, key string, v expr.Value) *expr.Dictionary { | |||||
| 77 | return out | 77 | return out | ||
| 78 | } | 78 | } | ||
| 79 | 79 | ||||
| 80 | + | // setKeySorted is setKey for the github context: a key that is not there yet | |||
| 81 | + | // goes before the first key that sorts after it (bytewise; "token" stays first) | |||
| 82 | + | // instead of at the end, so that the keys the runner adds (workspace, | |||
| 83 | + | // secret_source, the file commands...) sit where the ermite-conformance | |||
| 84 | + | // recordings list them. Note that the recordings are normalised with sorted | |||
| 85 | + | // keys, so they show the sorted order, not necessarily the insertion order of | |||
| 86 | + | // GitHub's runner (SetGitHubContext appends). Keys that exist are replaced in | |||
| 87 | + | // place. | |||
| 88 | + | func setKeySorted(d *expr.Dictionary, key string, v expr.Value) *expr.Dictionary { | |||
| 89 | + | for _, p := range d.Pairs() { | |||
| 90 | + | if strings.EqualFold(p.Key, key) { | |||
| 91 | + | return setKey(d, key, v) | |||
| 92 | + | } | |||
| 93 | + | } | |||
| 94 | + | out := expr.NewDictionary() | |||
| 95 | + | done := false | |||
| 96 | + | for _, p := range d.Pairs() { | |||
| 97 | + | if !done && p.Key > key && !strings.EqualFold(p.Key, "token") { | |||
| 98 | + | out.Add(key, v) | |||
| 99 | + | done = true | |||
| 100 | + | } | |||
| 101 | + | out.Add(p.Key, p.Value) | |||
| 102 | + | } | |||
| 103 | + | if !done { | |||
| 104 | + | out.Add(key, v) | |||
| 105 | + | } | |||
| 106 | + | return out | |||
| 107 | + | } | |||
| 108 | + | ||||
| 80 | func str(d *expr.Dictionary, key string) string { | 109 | func str(d *expr.Dictionary, key string) string { | ||
| 81 | if v, ok := d.Get(key); ok { | 110 | if v, ok := d.Get(key); ok { | ||
| 82 | if s, ok := v.(expr.String); ok { | 111 | if s, ok := v.(expr.String); ok { | ||
| @@ -222,7 +251,7 @@ func NewState(contextsJSON, matrixJSON, jobKey string, r RunnerInfo, repoFallbac | |||||
| 222 | } | 251 | } | ||
| 223 | } | 252 | } | ||
| 224 | if str(gh, "job") == "" && jobKey != "" { | 253 | if str(gh, "job") == "" && jobKey != "" { | ||
| 225 | - | gh = setKey(gh, "job", expr.String(jobKey)) | 254 | + | gh = setKeySorted(gh, "job", expr.String(jobKey)) |
| 226 | } | 255 | } | ||
| 227 | repo := repoName(str(gh, "repository")) | 256 | repo := repoName(str(gh, "repository")) | ||
| 228 | if repo == "" { | 257 | if repo == "" { | ||
| @@ -233,7 +262,7 @@ func NewState(contextsJSON, matrixJSON, jobKey string, r RunnerInfo, repoFallbac | |||||
| 233 | } | 262 | } | ||
| 234 | home := "/home/runner" | 263 | home := "/home/runner" | ||
| 235 | s.layout = NewLayout(home, repo, "") | 264 | s.layout = NewLayout(home, repo, "") | ||
| 236 | - | s.github = setKey(gh, "workspace", expr.String(s.layout.Workspace)) | 265 | + | s.github = setKeySorted(gh, "workspace", expr.String(s.layout.Workspace)) |
| 237 | return s, s.layout, s.initRest(root, matrixJSON, r) | 266 | return s, s.layout, s.initRest(root, matrixJSON, r) | ||
| 238 | } | 267 | } | ||
| 239 | 268 | ||||
| @@ -241,7 +270,7 @@ func NewState(contextsJSON, matrixJSON, jobKey string, r RunnerInfo, repoFallbac | |||||
| 241 | // must be called before the first step. | 270 | // must be called before the first step. | ||
| 242 | func (s *State) WithLayout(l Layout) { | 271 | func (s *State) WithLayout(l Layout) { | ||
| 243 | s.layout = l | 272 | s.layout = l | ||
| 244 | - | s.github = setKey(s.github, "workspace", expr.String(l.Workspace)) | 273 | + | s.github = setKeySorted(s.github, "workspace", expr.String(l.Workspace)) |
| 245 | s.runner = setKey(s.runner, "temp", expr.String(l.Temp)) | 274 | s.runner = setKey(s.runner, "temp", expr.String(l.Temp)) | ||
| 246 | s.runner = setKey(s.runner, "tool_cache", expr.String(l.ToolCache)) | 275 | s.runner = setKey(s.runner, "tool_cache", expr.String(l.ToolCache)) | ||
| 247 | s.runner = setKey(s.runner, "workspace", expr.String(l.RunnerWorkspace)) | 276 | s.runner = setKey(s.runner, "workspace", expr.String(l.RunnerWorkspace)) | ||
| @@ -315,7 +344,7 @@ func (s *State) initRest(root *expr.Dictionary, matrixJSON string, r RunnerInfo) | |||||
| 315 | func (s *State) Layout() Layout { return s.layout } | 344 | func (s *State) Layout() Layout { return s.layout } | ||
| 316 | 345 | ||||
| 317 | // SetGitHub sets a key of the github context (SetGitHubContext). | 346 | // SetGitHub sets a key of the github context (SetGitHubContext). | ||
| 318 | - | func (s *State) SetGitHub(k, v string) { s.github = setKey(s.github, k, expr.String(v)) } | 347 | + | func (s *State) SetGitHub(k, v string) { s.github = setKeySorted(s.github, k, expr.String(v)) } |
| 319 | 348 | ||||
| 320 | // GitHub returns a string key of the github context ("" when absent). | 349 | // GitHub returns a string key of the github context ("" when absent). | ||
| 321 | func (s *State) GitHub(k string) string { return str(s.github, k) } | 350 | func (s *State) GitHub(k string) string { return str(s.github, k) } | ||
M internal/executor/contexts_test.go +18 -0
| @@ -251,3 +251,21 @@ func TestWithLayout(t *testing.T) { | |||
| 251 | 251 | } | |
| 252 | 252 | } | |
| 253 | 253 | } | |
| 254 | + | ||
| 255 | + | func TestGitHubKeysAddedByTheRunnerKeepTheSortedOrder(t *testing.T) { | |
| 256 | + | // A server that lists its keys sorted, as the recordings do: the keys the | |
| 257 | + | // runner adds land at their sorted place, replaced keys stay, token is first. | |
| 258 | + | s, _, err := NewState(`{"github":{"actor":"a","event_name":"push","job":"j","repository":"o/r","sha":"s","zzz":"1"}}`, "", "j", RunnerInfo{}, "") | |
| 259 | + | if err != nil { | |
| 260 | + | t.Fatal(err) | |
| 261 | + | } | |
| 262 | + | for _, k := range []string{"secret_source", "artifacts", "action", "event_path"} { | |
| 263 | + | s.SetGitHub(k, "x") | |
| 264 | + | } | |
| 265 | + | s.SetGitHub("sha", "t") | |
| 266 | + | got := strings.Join(s.Values(nil)["github"].(*expr.Dictionary).Keys(), ",") | |
| 267 | + | want := "token,action,actor,artifacts,event_name,event_path,job,repository,secret_source,sha,workspace,zzz" | |
| 268 | + | if got != want { | |
| 269 | + | t.Errorf("keys\n got %s\nwant %s", got, want) | |
| 270 | + | } | |
| 271 | + | } | |
| @@ -251,3 +251,21 @@ func TestWithLayout(t *testing.T) { | |||||
| 251 | } | 251 | } | ||
| 252 | } | 252 | } | ||
| 253 | } | 253 | } | ||
| 254 | + | ||||
| 255 | + | func TestGitHubKeysAddedByTheRunnerKeepTheSortedOrder(t *testing.T) { | |||
| 256 | + | // A server that lists its keys sorted, as the recordings do: the keys the | |||
| 257 | + | // runner adds land at their sorted place, replaced keys stay, token is first. | |||
| 258 | + | s, _, err := NewState(`{"github":{"actor":"a","event_name":"push","job":"j","repository":"o/r","sha":"s","zzz":"1"}}`, "", "j", RunnerInfo{}, "") | |||
| 259 | + | if err != nil { | |||
| 260 | + | t.Fatal(err) | |||
| 261 | + | } | |||
| 262 | + | for _, k := range []string{"secret_source", "artifacts", "action", "event_path"} { | |||
| 263 | + | s.SetGitHub(k, "x") | |||
| 264 | + | } | |||
| 265 | + | s.SetGitHub("sha", "t") | |||
| 266 | + | got := strings.Join(s.Values(nil)["github"].(*expr.Dictionary).Keys(), ",") | |||
| 267 | + | want := "token,action,actor,artifacts,event_name,event_path,job,repository,secret_source,sha,workspace,zzz" | |||
| 268 | + | if got != want { | |||
| 269 | + | t.Errorf("keys\n got %s\nwant %s", got, want) | |||
| 270 | + | } | |||
| 271 | + | } | |||