tenseleyflow/shithub / be95514

Browse files

actions: harden live runner smoke path

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
be9551470a5b3b0d44d0d02d386e5e31689c77f5
Parents
9bd52be
Tree
e51a7f5

4 changed files

StatusFile+-
M internal/runner/engine/docker.go 1 1
M internal/runner/engine/docker_test.go 1 1
M internal/web/handlers/api/api.go 0 1
M internal/web/handlers/api/runners_test.go 49 0
internal/runner/engine/docker.gomodified
@@ -378,7 +378,7 @@ func (d *Docker) dockerInvocation(job Job, step Step) (dockerInvocation, error)
378
 		"--ulimit", "nproc=" + defaultNprocLimit,
378
 		"--ulimit", "nproc=" + defaultNprocLimit,
379
 		"--user", user,
379
 		"--user", user,
380
 		"--workdir=" + workdir,
380
 		"--workdir=" + workdir,
381
-		"--mount", "type=bind,src=" + job.WorkspaceDir + ",dst=/workspace,rw",
381
+		"--mount", "type=bind,src=" + job.WorkspaceDir + ",dst=/workspace",
382
 	}
382
 	}
383
 	for _, dns := range d.cfg.DNSServers {
383
 	for _, dns := range d.cfg.DNSServers {
384
 		dns = strings.TrimSpace(dns)
384
 		dns = strings.TrimSpace(dns)
internal/runner/engine/docker_test.gomodified
@@ -153,7 +153,7 @@ func TestDockerExecute_BuildsResourceCappedRunCommand(t *testing.T) {
153
 	if !reflect.DeepEqual(rec.args, want) {
153
 	if !reflect.DeepEqual(rec.args, want) {
154
 		t.Fatalf("args:\ngot  %#v\nwant %#v", rec.args, want)
154
 		t.Fatalf("args:\ngot  %#v\nwant %#v", rec.args, want)
155
 	}
155
 	}
156
-	if !strings.HasPrefix(rec.args[25], "type=bind,src=") || !strings.HasSuffix(rec.args[25], ",dst=/workspace,rw") {
156
+	if !strings.HasPrefix(rec.args[25], "type=bind,src=") || !strings.HasSuffix(rec.args[25], ",dst=/workspace") || strings.Contains(rec.args[25], ",rw") {
157
 		t.Fatalf("workspace mount arg: %q", rec.args[25])
157
 		t.Fatalf("workspace mount arg: %q", rec.args[25])
158
 	}
158
 	}
159
 	if wantEnv := []string{"A=job", "B=step"}; !reflect.DeepEqual(rec.env, wantEnv) {
159
 	if wantEnv := []string{"A=job", "B=step"}; !reflect.DeepEqual(rec.env, wantEnv) {
internal/web/handlers/api/api.gomodified
@@ -113,7 +113,6 @@ func (h *Handlers) Mount(r chi.Router) {
113
 	})
113
 	})
114
 	r.Group(func(r chi.Router) {
114
 	r.Group(func(r chi.Router) {
115
 		r.Use(middleware.MaxBodySize(runnerAPIMaxBodyBytes))
115
 		r.Use(middleware.MaxBodySize(runnerAPIMaxBodyBytes))
116
-		r.Use(apiLimitMW)
117
 		h.mountRunners(r)
116
 		h.mountRunners(r)
118
 	})
117
 	})
119
 	r.Group(func(r chi.Router) {
118
 	r.Group(func(r chi.Router) {
internal/web/handlers/api/runners_test.gomodified
@@ -32,11 +32,13 @@ import (
32
 	"github.com/tenseleyFlow/shithub/internal/auth/secretbox"
32
 	"github.com/tenseleyFlow/shithub/internal/auth/secretbox"
33
 	"github.com/tenseleyFlow/shithub/internal/infra/metrics"
33
 	"github.com/tenseleyFlow/shithub/internal/infra/metrics"
34
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
34
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
35
+	"github.com/tenseleyFlow/shithub/internal/ratelimit"
35
 	repogit "github.com/tenseleyFlow/shithub/internal/repos/git"
36
 	repogit "github.com/tenseleyFlow/shithub/internal/repos/git"
36
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
37
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
37
 	"github.com/tenseleyFlow/shithub/internal/testing/dbtest"
38
 	"github.com/tenseleyFlow/shithub/internal/testing/dbtest"
38
 	usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
39
 	usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
39
 	apih "github.com/tenseleyFlow/shithub/internal/web/handlers/api"
40
 	apih "github.com/tenseleyFlow/shithub/internal/web/handlers/api"
41
+	"github.com/tenseleyFlow/shithub/internal/web/handlers/api/apilimit"
40
 	workerdb "github.com/tenseleyFlow/shithub/internal/worker/sqlc"
42
 	workerdb "github.com/tenseleyFlow/shithub/internal/worker/sqlc"
41
 )
43
 )
42
 
44
 
@@ -186,6 +188,53 @@ func TestRunnerHeartbeatClaimsQueuedJob(t *testing.T) {
186
 	}
188
 	}
187
 }
189
 }
188
 
190
 
191
+func TestRunnerHeartbeatBypassesGlobalAnonAPILimit(t *testing.T) {
192
+	pool := dbtest.NewTestDB(t)
193
+	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
194
+	token, _ := registerRunnerForTest(t, pool, []string{"ubuntu-latest", "linux"}, 1)
195
+	signer := runnerAPISigner(t, time.Date(2026, 5, 10, 12, 0, 0, 0, time.UTC))
196
+
197
+	h, err := apih.New(apih.Deps{
198
+		Pool:        pool,
199
+		Logger:      logger,
200
+		BaseURL:     "https://shithub.test",
201
+		RunnerJWT:   signer,
202
+		RateLimiter: ratelimit.New(pool),
203
+		APILimit: apilimit.Config{
204
+			AuthedPerHour: 1,
205
+			AnonPerHour:   1,
206
+			Logger:        logger,
207
+		},
208
+	})
209
+	if err != nil {
210
+		t.Fatalf("api.New: %v", err)
211
+	}
212
+	router := chi.NewRouter()
213
+	h.Mount(router)
214
+
215
+	req := httptest.NewRequest(http.MethodGet, "/api/v1/meta", nil)
216
+	req.RemoteAddr = "10.0.0.77:12345"
217
+	rr := httptest.NewRecorder()
218
+	router.ServeHTTP(rr, req)
219
+	if rr.Code != http.StatusOK {
220
+		t.Fatalf("meta status: got %d, want 200; body=%s", rr.Code, rr.Body.String())
221
+	}
222
+
223
+	req = httptest.NewRequest(http.MethodPost, "/api/v1/runners/heartbeat",
224
+		strings.NewReader(`{"labels":["ubuntu-latest","linux"],"capacity":1}`))
225
+	req.Header.Set("Authorization", "Bearer "+token)
226
+	req.RemoteAddr = "10.0.0.77:12346"
227
+	rr = httptest.NewRecorder()
228
+	router.ServeHTTP(rr, req)
229
+
230
+	if rr.Code != http.StatusNoContent {
231
+		t.Fatalf("heartbeat status: got %d, want 204; body=%s", rr.Code, rr.Body.String())
232
+	}
233
+	if got := rr.Header().Get("X-RateLimit-Limit"); got != "60" {
234
+		t.Errorf("runner heartbeat limit header: got %q, want 60", got)
235
+	}
236
+}
237
+
189
 func TestRunnerSecretsAreClaimedAndServerScrubsLogs(t *testing.T) {
238
 func TestRunnerSecretsAreClaimedAndServerScrubsLogs(t *testing.T) {
190
 	ctx := context.Background()
239
 	ctx := context.Background()
191
 	pool := dbtest.NewTestDB(t)
240
 	pool := dbtest.NewTestDB(t)