vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Let a job start service containers beside it
c1929d2fceb5e9e65204d5eef907d0781b9a5391 · Keenan Nemetz · 5d ago · parent 1d06b008
ci/agent/validate_test.go +58
| @@ -155,3 +155,61 @@ func TestLeasedSecretsAreRevalidated(t *testing.T) { | |||
| 155 | 155 | t.Fatalf("a valid job with secrets was refused: %v", err) | |
| 156 | 156 | } | |
| 157 | 157 | } | |
| 158 | |||
| 159 | // Services arrive in the lease, so the runner checks the names, images, and | ||
| 160 | // environment it is about to turn into container arguments. | ||
| 161 | func TestServicesAreRevalidated(t *testing.T) { | ||
| 162 | t.Parallel() | ||
| 163 | valid := func() *runnerJob { | ||
| 164 | j := goodJob(t) | ||
| 165 | j.Network = ci.NetworkBridge | ||
| 166 | j.Services = []runnerService{{ | ||
| 167 | ID: "db", | ||
| 168 | Image: "postgres:17", | ||
| 169 | Env: map[string]string{"POSTGRES_PASSWORD": "test"}, | ||
| 170 | }} | ||
| 171 | return j | ||
| 172 | } | ||
| 173 | if err := validateJob(valid()); err != nil { | ||
| 174 | t.Fatalf("a valid service job was refused: %v", err) | ||
| 175 | } | ||
| 176 | cases := []struct { | ||
| 177 | name string | ||
| 178 | mutate func(*runnerJob) | ||
| 179 | want string | ||
| 180 | }{ | ||
| 181 | {"no bridge", func(j *runnerJob) { j.Network = "" }, "network: bridge"}, | ||
| 182 | {"none", func(j *runnerJob) { j.Network = ci.NetworkNone }, "network: bridge"}, | ||
| 183 | {"bad name", func(j *runnerJob) { j.Services[0].ID = "DB" }, "service name"}, | ||
| 184 | {"duplicate name", func(j *runnerJob) { | ||
| 185 | j.Services = append(j.Services, j.Services[0]) | ||
| 186 | }, "listed twice"}, | ||
| 187 | {"no image", func(j *runnerJob) { j.Services[0].Image = "" }, "image"}, | ||
| 188 | {"image file", func(j *runnerJob) { j.Services[0].Image = "file:db" }, "reserved for image-file"}, | ||
| 189 | {"bad env", func(j *runnerJob) { j.Services[0].Env = map[string]string{"A B": "1"} }, "variable name"}, | ||
| 190 | {"multiline env", func(j *runnerJob) { j.Services[0].Env = map[string]string{"A": "1\n2"} }, "one line"}, | ||
| 191 | } | ||
| 192 | for _, c := range cases { | ||
| 193 | job := valid() | ||
| 194 | c.mutate(job) | ||
| 195 | err := validateJob(job) | ||
| 196 | if err == nil { | ||
| 197 | t.Fatalf("%s: accepted", c.name) | ||
| 198 | } | ||
| 199 | if !strings.Contains(err.Error(), c.want) { | ||
| 200 | t.Fatalf("%s: error does not mention %q: %v", c.name, c.want, err) | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | func TestTooManyServicesAreRefused(t *testing.T) { | ||
| 206 | t.Parallel() | ||
| 207 | j := goodJob(t) | ||
| 208 | j.Network = ci.NetworkBridge | ||
| 209 | for i := 0; i <= ci.MaxServices; i++ { | ||
| 210 | j.Services = append(j.Services, runnerService{ID: "s" + string(rune('a'+i)), Image: "alpine:3.22"}) | ||
| 211 | } | ||
| 212 | if err := validateJob(j); err == nil || !strings.Contains(err.Error(), "services, limit") { | ||
| 213 | t.Fatalf("too many services: %v", err) | ||
| 214 | } | ||
| 215 | } | ||