Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Add remote administration and local workflow runs

3abbe749096f4d7ff76f4d468e4427829b8a6f95 · Keenan Nemetz · 16d ago · parent 520470e1

Add remote administration and local workflow runs

Make `vole admin` the single namespace for user, repository, and access
management, runnable locally or over SSH by instance admins. Instance-admin
grants live in users.db, and the audit log names the actor separately from the
account being changed.

Add `vole workflow run` to execute workflow jobs in the working tree without a
server or runner. Positional arguments select workflows, --job selects jobs, and
the command reuses the push-time validator.

Update the README and guides for the new command shapes.

forge/identity/identity_test.go +71

@@ -424,3 +424,74 @@ func equal(got, want []string) bool {
424 424 }
425 425 return true
426 426 }
427
428 func TestInstanceAdminsAreExplicitAndAudited(t *testing.T) {
429 t.Parallel()
430 s := newStore(t)
431 alice, err := s.Create("alice", "operator")
432 if err != nil {
433 t.Fatal(err)
434 }
435 if s.IsAdmin(alice.ID) {
436 t.Fatal("a new account is an instance admin")
437 }
438 if _, err := s.SetAdmin("alice", true, "operator"); err != nil {
439 t.Fatal(err)
440 }
441 if !s.IsAdmin(alice.ID) {
442 t.Fatal("alice was not made an instance admin")
443 }
444 if a, err := s.Find("alice"); err != nil || !a.Admin {
445 t.Fatalf("admin flag was not loaded into the account: %+v, %v", a, err)
446 }
447 log, err := os.ReadFile(s.AuditPath)
448 if err != nil {
449 t.Fatal(err)
450 }
451 if !strings.Contains(string(log), "identity.grant-admin\toperator\talice") {
452 t.Fatalf("admin audit line does not name the actor:\n%s", log)
453 }
454 if _, err := s.Create("bob", "operator"); err != nil {
455 t.Fatal(err)
456 }
457 if _, err := s.SetAdmin("bob", true, "operator"); err != nil {
458 t.Fatal(err)
459 }
460 if _, err := s.SetAdmin("alice", false, "operator"); err != nil {
461 t.Fatal(err)
462 }
463 if s.IsAdmin(alice.ID) {
464 t.Fatal("admin grant survived revocation")
465 }
466 }
467
468 func TestLastInstanceAdminCannotBeRemoved(t *testing.T) {
469 t.Parallel()
470 s := newStore(t)
471 alice, err := s.Create("alice", "operator")
472 if err != nil {
473 t.Fatal(err)
474 }
475 bob, err := s.Create("bob", "operator")
476 if err != nil {
477 t.Fatal(err)
478 }
479 if _, err := s.SetAdmin(alice.ID, true, "operator"); err != nil {
480 t.Fatal(err)
481 }
482 if _, err := s.SetAdmin(alice.ID, false, "operator"); err == nil {
483 t.Fatal("revoked the last instance admin")
484 }
485 if _, err := s.Retire(alice.ID, "operator"); err == nil {
486 t.Fatal("retired the last instance admin")
487 }
488 if _, err := s.SetAdmin(bob.ID, true, "operator"); err != nil {
489 t.Fatal(err)
490 }
491 if _, err := s.SetAdmin(alice.ID, false, "operator"); err != nil {
492 t.Fatalf("could not revoke an admin when another remained: %v", err)
493 }
494 if _, err := s.Retire(bob.ID, "operator"); err == nil {
495 t.Fatal("retired the last instance admin after revoking the other")
496 }
497 }