Commit c21c7ea735
Verified · cmc
Layout: unified · split
internal/control/build.go +23 −2
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | "fmt" | 6 | "fmt" |
| 7 | "io" | 7 | "io" |
| 8 | "log/slog" | 8 | "log/slog" |
| 9 | "net" | ||
| 9 | "regexp" | 10 | "regexp" |
| 10 | "slices" | 11 | "slices" |
| 11 | "strconv" | 12 | "strconv" |
| @@ -460,6 +461,23 @@ func runnerMayBuild(c *Ctx, key store.SSHKey, repoID int64) (bool, error) { | |||
| 460 | // walking it forever. | 461 | // walking it forever. |
| 461 | const maxOrphanSkip = 50 | 462 | const maxOrphanSkip = 50 |
| 462 | 463 | ||
| 464 | // publicSSH is the instance's ssh destination as anyone outside reaches | ||
| 465 | // it. A runner on the daemon's own host polls over loopback and hands | ||
| 466 | // its builds this instead, so no build connects from the runner's source | ||
| 467 | // address (#260). The port is added only when it is not 22: hutch and | ||
| 468 | // orgo build ssh://$GITBAY_SSH/... URLs, valid in both forms. Empty when | ||
| 469 | // site_url is not set. | ||
| 470 | func publicSSH(c *Ctx) string { | ||
| 471 | host := c.Cfg.SiteHost() | ||
| 472 | if host == "" { | ||
| 473 | return "" | ||
| 474 | } | ||
| 475 | if p := c.Cfg.SSH.Port; p != 0 && p != 22 { | ||
| 476 | return "git@" + net.JoinHostPort(host, strconv.Itoa(p)) | ||
| 477 | } | ||
| 478 | return "git@" + host | ||
| 479 | } | ||
| 480 | |||
| 463 | func runRunnerNext(c *Ctx, args []string) int { | 481 | func runRunnerNext(c *Ctx, args []string) int { |
| 464 | key, code := runnerSession(c) | 482 | key, code := runnerSession(c) |
| 465 | if code >= 0 { | 483 | if code >= 0 { |
| @@ -562,10 +580,13 @@ func runRunnerNext(c *Ctx, args []string) int { | |||
| 562 | Image string `json:"image,omitempty"` | 580 | Image string `json:"image,omitempty"` |
| 563 | // Trusted is always sent: a runner decides a build's home and | 581 | // Trusted is always sent: a runner decides a build's home and |
| 564 | // secrets from it, and reads a missing field as untrusted (#255). | 582 | // secrets from it, and reads a missing field as untrusted (#255). |
| 565 | Trusted bool `json:"trusted"` | 583 | Trusted bool `json:"trusted"` |
| 584 | // SSH is the instance's public destination for the build's | ||
| 585 | // GITBAY_SSH when its runner polls over loopback (#260). | ||
| 586 | SSH string `json:"ssh,omitempty"` | ||
| 566 | Secrets map[string]string `json:"secrets,omitempty"` | 587 | Secrets map[string]string `json:"secrets,omitempty"` |
| 567 | }{ID: b.ID, Repo: repo.Path(), Number: b.Number, Job: b.Job, SHA: b.SHA, Ref: b.Ref, | 588 | }{ID: b.ID, Repo: repo.Path(), Number: b.Number, Job: b.Job, SHA: b.SHA, Ref: b.Ref, |
| 568 | Steps: steps, Image: b.Image, Trusted: b.Trusted, Secrets: secrets} | 589 | Steps: steps, Image: b.Image, Trusted: b.Trusted, SSH: publicSSH(c), Secrets: secrets} |
| 569 | return c.emit(d, func(w io.Writer) { | 590 | return c.emit(d, func(w io.Writer) { |
| 570 | fmt.Fprintf(w, "build %d: %s %s @ %.10s\n", d.ID, d.Repo, d.Job, d.SHA) | 591 | fmt.Fprintf(w, "build %d: %s %s @ %.10s\n", d.ID, d.Repo, d.Job, d.SHA) |
| 571 | }) | 592 | }) |
internal/control/runnernext_test.go +29
| @@ -266,3 +266,32 @@ func TestRunnerNextSaysWhetherTrusted(t *testing.T) { | |||
| 266 | } | 266 | } |
| 267 | } | 267 | } |
| 268 | } | 268 | } |
| 269 | |||
| 270 | // A build on the daemon's own host is given the public destination, not | ||
| 271 | // the loopback address its runner polls. The port rides along only when | ||
| 272 | // it is not 22, so ssh://$GITBAY_SSH/<owner>/<name>.git is a valid URL | ||
| 273 | // either way (#260). | ||
| 274 | func TestRunnerNextCarriesPublicSSH(t *testing.T) { | ||
| 275 | st, repo, uid, root, baseSHA, _ := setupOrphanRepo(t) | ||
| 276 | for _, tc := range []struct { | ||
| 277 | port int | ||
| 278 | want string | ||
| 279 | }{ | ||
| 280 | {0, `"ssh":"git@x.test"`}, | ||
| 281 | {22, `"ssh":"git@x.test"`}, | ||
| 282 | {2022, `"ssh":"git@x.test:2022"`}, | ||
| 283 | } { | ||
| 284 | if _, err := st.CreateBuild(repo.ID, "unit", baseSHA, "main", "[]", "", "", true); err != nil { | ||
| 285 | t.Fatal(err) | ||
| 286 | } | ||
| 287 | c, out := runnerCtx(st, uid, root) // site_url https://x.test | ||
| 288 | c.Cfg.SSH.Port = tc.port | ||
| 289 | c.JSON = true | ||
| 290 | if code := runRunnerNext(c, nil); code != protocol.ExitOK { | ||
| 291 | t.Fatalf("port %d: runner next: exit %d, output:\n%s", tc.port, code, out.String()) | ||
| 292 | } | ||
| 293 | if !strings.Contains(out.String(), tc.want) { | ||
| 294 | t.Fatalf("port %d: claim lacks %s:\n%s", tc.port, tc.want, out.String()) | ||
| 295 | } | ||
| 296 | } | ||
| 297 | } | ||