Commit b022fedfc0
Verified · cmc
Layout: unified · split
e2e/mrweb_test.go +1 −1
| @@ -272,7 +272,7 @@ func TestMRListRows(t *testing.T) { | |||
| 272 | t.Fatalf("mr create: %s", errOut) | 272 | t.Fatalf("mr create: %s", errOut) |
| 273 | } | 273 | } |
| 274 | if _, errOut, code := inst.ssh(t, aliceKey, "", "status", "set", "alice/lib", sha, | 274 | if _, errOut, code := inst.ssh(t, aliceKey, "", "status", "set", "alice/lib", sha, |
| 275 | "--context", "ci/test", "--state", "success"); code != 0 { | 275 | "--context", "ext/test", "--state", "success"); code != 0 { |
| 276 | t.Fatalf("status set: %s", errOut) | 276 | t.Fatalf("status set: %s", errOut) |
| 277 | } | 277 | } |
| 278 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "comment", "alice/lib", "1", | 278 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "comment", "alice/lib", "1", |
e2e/readonly_test.go +1 −1
| @@ -72,7 +72,7 @@ func TestReadOnlyCommandsWriteNothing(t *testing.T) { | |||
| 72 | must("", "milestone", "create", "alice/app", "m1") | 72 | must("", "milestone", "create", "alice/app", "m1") |
| 73 | must("", "mr", "create", "alice/app", "--source", "feat", "--target", "main", "--title", "change") | 73 | must("", "mr", "create", "alice/app", "--source", "feat", "--target", "main", "--title", "change") |
| 74 | must("", "mr", "diff-comment", "alice/app", "1", "--path", "f.go", "--line", "3", "--message", "why") | 74 | must("", "mr", "diff-comment", "alice/app", "1", "--path", "f.go", "--line", "3", "--message", "why") |
| 75 | must("", "status", "set", "alice/app", sha, "--context", "ci/x", "--state", "success") | 75 | must("", "status", "set", "alice/app", sha, "--context", "ext/x", "--state", "success") |
| 76 | must("", "release", "create", "alice/app", "v1", "--title", "first") | 76 | must("", "release", "create", "alice/app", "v1", "--title", "first") |
| 77 | must("data\n", "release", "asset", "add", "alice/app", "v1", "a.txt") | 77 | must("data\n", "release", "asset", "add", "alice/app", "v1", "a.txt") |
| 78 | snippetOut := must("hello\n", "snippet", "create", "a.txt", "--json") | 78 | snippetOut := must("hello\n", "snippet", "create", "a.txt", "--json") |
e2e/status_test.go +5
| @@ -48,6 +48,11 @@ func TestCommitStatuses(t *testing.T) { | |||
| 48 | t.Fatal("reader reported a status") | 48 | t.Fatal("reader reported a status") |
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | // ci/ is the instance's own: a writer is refused it (#258). | ||
| 52 | if _, errOut, code := inst.ssh(t, bobKey, "", "status", "set", "alice/svc", head, "--context", "ci/build", "--state", "success"); code != 4 || !strings.Contains(errOut, "reserved") { | ||
| 53 | t.Fatalf("writer posted a ci/ status: exit %d, %s", code, errOut) | ||
| 54 | } | ||
| 55 | |||
| 51 | // Bob (write) reports pending, then success: upsert, not duplicate. | 56 | // Bob (write) reports pending, then success: upsert, not duplicate. |
| 52 | if _, errOut, code := inst.ssh(t, bobKey, "", "status", "set", "alice/svc", head, | 57 | if _, errOut, code := inst.ssh(t, bobKey, "", "status", "set", "alice/svc", head, |
| 53 | "--context", "build", "--state", "pending", "--description", "'compiling'"); code != 0 { | 58 | "--context", "build", "--state", "pending", "--description", "'compiling'"); code != 0 { |
internal/control/status.go +10 −2
| @@ -16,13 +16,13 @@ func init() { | |||
| 16 | Summary: "report a commit status (CI)", | 16 | Summary: "report a commit status (CI)", |
| 17 | Usage: "status set <owner/name> <sha> --context <c> --state pending|success|failure|error [--description <d>] [--url <u>]", | 17 | Usage: "status set <owner/name> <sha> --context <c> --state pending|success|failure|error [--description <d>] [--url <u>]", |
| 18 | Flags: []Flag{ | 18 | Flags: []Flag{ |
| 19 | {"--context", "<c>", "the check this status reports for", ""}, | 19 | {"--context", "<c>", "the check this status reports for; ci/ is reserved for the instance's builds", ""}, |
| 20 | {"--state", "pending|success|failure|error", "the check's outcome", ""}, | 20 | {"--state", "pending|success|failure|error", "the check's outcome", ""}, |
| 21 | {"--description", "<d>", "short text shown beside the state", ""}, | 21 | {"--description", "<d>", "short text shown beside the state", ""}, |
| 22 | {"--url", "<u>", "link to the check's own output", ""}, | 22 | {"--url", "<u>", "link to the check's own output", ""}, |
| 23 | }, | 23 | }, |
| 24 | Examples: []string{ | 24 | Examples: []string{ |
| 25 | "status set krz/gitbay a1b2c3d --context ci/build --state success", | 25 | "status set krz/gitbay a1b2c3d --context ext/lint --state success", |
| 26 | }, | 26 | }, |
| 27 | Run: runStatusSet}) | 27 | Run: runStatusSet}) |
| 28 | register(Command{Path: []string{"status", "list"}, | 28 | register(Command{Path: []string{"status", "list"}, |
| @@ -68,6 +68,14 @@ func runStatusSet(c *Ctx, args []string) int { | |||
| 68 | if path == "" || sha == "" || context == "" || !validStatusState[state] { | 68 | if path == "" || sha == "" || context == "" || !validStatusState[state] { |
| 69 | return c.usage() | 69 | return c.usage() |
| 70 | } | 70 | } |
| 71 | // ci/<job> statuses are the build subsystem's: queued, reused, | ||
| 72 | // skipped and finished by the server itself. A writer who could post | ||
| 73 | // one could mark ci/test green on their own head before, or instead | ||
| 74 | // of, the build (#258). Case-folded, so CI/test is no way around it. | ||
| 75 | if strings.HasPrefix(strings.ToLower(context), "ci/") { | ||
| 76 | return c.fail(protocol.ExitDenied, "the ci/ prefix is reserved for the instance's builds; report under another name, such as ext/%s", | ||
| 77 | strings.TrimPrefix(strings.ToLower(context), "ci/")) | ||
| 78 | } | ||
| 71 | if url != "" && !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { | 79 | if url != "" && !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { |
| 72 | return c.fail(protocol.ExitUsage, "--url must be http(s)") | 80 | return c.fail(protocol.ExitUsage, "--url must be http(s)") |
| 73 | } | 81 | } |
internal/control/status_test.go added +26
| @@ -0,0 +1,26 @@ | |||
| 1 | package control | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "strings" | ||
| 5 | "testing" | ||
| 6 | |||
| 7 | "gitbay.org/gitbay/internal/protocol" | ||
| 8 | "gitbay.org/gitbay/internal/store" | ||
| 9 | ) | ||
| 10 | |||
| 11 | // ci/<job> statuses are the build subsystem's. A writer who could post | ||
| 12 | // one could mark ci/test green on their own head before, or instead of, | ||
| 13 | // the build (#258). | ||
| 14 | func TestStatusSetRefusesReservedContext(t *testing.T) { | ||
| 15 | st, repo, uid := newQueueTestRepo(t) | ||
| 16 | for _, ctx := range []string{"ci/test", "CI/test", "ci/"} { | ||
| 17 | c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid, Username: "alice"}) | ||
| 18 | code := Dispatch(c, []string{"status", "set", repo.Path(), "abc1234", "--context", ctx, "--state", "success"}) | ||
| 19 | if code != protocol.ExitDenied || !strings.Contains(errOut.String(), "reserved") { | ||
| 20 | t.Errorf("--context %s: exit %d, %s", ctx, code, errOut.String()) | ||
| 21 | } | ||
| 22 | } | ||
| 23 | if has, err := st.RepoHasStatuses(repo.ID); err != nil || has { | ||
| 24 | t.Fatalf("a refused status was stored: %v %v", has, err) | ||
| 25 | } | ||
| 26 | } | ||