mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Merge pull request #14207 from EtiennePerot:maintainers
PiperOrigin-RevId: 968193941
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: update-gvisor-maintainers
|
||||
description: >
|
||||
Update the gVisor maintainer roster in governance/maintainers.yaml and regenerate
|
||||
the files derived from it (.github/reviewer.json and MAINTAINERS.md).
|
||||
Use when a maintainer goes on hiatus, returns from hiatus, is added, steps down to
|
||||
emeritus, changes employer, or when MAINTAINERS.md / reviewer.json are out of sync.
|
||||
---
|
||||
|
||||
# Update the gVisor maintainer roster
|
||||
|
||||
`governance/maintainers.yaml` is the single source of truth.
|
||||
`.github/reviewer.json` and `MAINTAINERS.md` are generated from it by
|
||||
`//governance/tools/maintainers:maintainers_gen`, and `maintainers_test`
|
||||
byte-compares both against the checked-in copies. So: edit the YAML, regenerate,
|
||||
run the test. Never hand-edit the two generated files; the test will catch you,
|
||||
and rightly so.
|
||||
|
||||
## Schema
|
||||
|
||||
Each entry under `maintainers:`:
|
||||
|
||||
```yaml
|
||||
- name: Gee Vaïzaur # Full name or nickname.
|
||||
github: gvaizaur # GitHub username.
|
||||
affiliation: Independent # Current employer, or "Independent".
|
||||
past_affiliation: # Optional.
|
||||
- affiliation: Arasaka Corporation
|
||||
until: 2025-03-01
|
||||
started: 2018-05-08 # First contribution. The list is sorted by this.
|
||||
status: HIATUS_SINCE:2026-07-17
|
||||
```
|
||||
|
||||
`status` is one of three, and it drives everything downstream:
|
||||
|
||||
Status | Reviews | Merge permissions | `reviewer.json` | `MAINTAINERS.md`
|
||||
--------------------------- | ------- | ----------------- | --------------- | ----------------
|
||||
`ACTIVE` | yes | yes | `true` | main table
|
||||
`HIATUS_SINCE:YYYY-MM-DD` | no | yes | `false` | main table
|
||||
`EMERITUS_SINCE:YYYY-MM-DD` | no | no | omitted | emeritus table
|
||||
|
||||
`true` in `reviewer.json` means the GitHub workflow may auto-assign reviews to
|
||||
them. `false` means they are still a maintainer, just not on the receiving end
|
||||
of the assignment lottery. Hiatus is about reviews only, not permissions; that
|
||||
is why hiatus maintainers still show up in the main `MAINTAINERS.md` table
|
||||
alongside active ones.
|
||||
|
||||
## Common changes
|
||||
|
||||
Most of these are a one-line `status:` edit on an existing entry:
|
||||
|
||||
- Goes on hiatus: `status: HIATUS_SINCE:<today>`.
|
||||
- Comes back from hiatus: `status: ACTIVE`. Drop the date; `ACTIVE` never
|
||||
carries one.
|
||||
- Steps down, or hits 12 months of inactivity: `status:
|
||||
EMERITUS_SINCE:<date>`.
|
||||
- Comes back from emeritus: `status: ACTIVE`, but only through the normal
|
||||
nomination process. See "Becoming a maintainer" in `GOVERNANCE.md`.
|
||||
|
||||
The two that are not:
|
||||
|
||||
- New maintainer: insert the entry in `started` order, not at the end of the
|
||||
file.
|
||||
- Changed employer: update `affiliation`, and move the old one to
|
||||
`past_affiliation` with the date it ended as `until`.
|
||||
|
||||
Dates are real calendar dates, so go look up today's date rather than guessing
|
||||
at one. If the user gave you a date, use theirs.
|
||||
|
||||
After any change, regenerate.
|
||||
|
||||
## Regenerate
|
||||
|
||||
```bash
|
||||
make run TARGETS=//governance/tools/maintainers:maintainers_gen \
|
||||
ARGS="-input governance/maintainers.yaml -format reviewer.json -output .github/reviewer.json"
|
||||
|
||||
make run TARGETS=//governance/tools/maintainers:maintainers_gen \
|
||||
ARGS="-input governance/maintainers.yaml -format MAINTAINERS.md -output MAINTAINERS.md"
|
||||
```
|
||||
|
||||
`-format` takes the name of the file being generated, `reviewer.json` or
|
||||
`MAINTAINERS.md`. Leave off `-output` to dump to stdout instead, which is handy
|
||||
for eyeballing a change before writing it.
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
make test TARGETS=//governance/tools/maintainers/...
|
||||
```
|
||||
|
||||
This fails if the YAML changed and you forgot to regenerate, or if someone
|
||||
edited a generated file by hand.
|
||||
|
||||
## Before handing back
|
||||
|
||||
Show the user the diff across all three files (`governance/maintainers.yaml`,
|
||||
`.github/reviewer.json`, `MAINTAINERS.md`) and let them confirm the roster reads
|
||||
the way they meant it to.
|
||||
|
||||
If a generated file contains something the generator does not emit, that content
|
||||
is drift, and regenerating is what removes it. Do not teach the generator to
|
||||
reproduce it without asking first: the generator decides what those files
|
||||
contain, not whatever happened to be committed.
|
||||
@@ -3,6 +3,7 @@
|
||||
"carzh": true,
|
||||
"etienneperot": true,
|
||||
"fvoznika": true,
|
||||
"kerumeto": true,
|
||||
"konstantin-s-bogom": true,
|
||||
"manninglucas": false,
|
||||
"milantracy": true,
|
||||
|
||||
@@ -20,6 +20,8 @@ exports_files([
|
||||
"README.md",
|
||||
"SECURITY.md",
|
||||
"GOVERNANCE.md",
|
||||
"MAINTAINERS.md",
|
||||
".github/reviewer.json",
|
||||
])
|
||||
|
||||
release_files(
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!-- Generated from governance/maintainers.yaml by
|
||||
//governance/tools/maintainers:maintainers_gen; do not edit directly. -->
|
||||
|
||||
The current maintainers of the gVisor project are listed below.
|
||||
|
||||
Name | GitHub ID | Company/Organization
|
||||
-------------------- | ------------------------------------------------------------ | --------------------
|
||||
Fabricio Voznika | [@fvoznika](https://github.com/fvoznika) | Google
|
||||
Jamie Liu | [@nixprime](https://github.com/nixprime) | Google
|
||||
Zach Koopmans | [@zkoopmans](https://github.com/zkoopmans) | Google
|
||||
Nayana Bidari | [@nybidari](https://github.com/nybidari) | Google
|
||||
Etienne Perot | [@etienneperot](https://github.com/etienneperot) | Google
|
||||
Lucas Manning | [@manninglucas](https://github.com/manninglucas) | Google
|
||||
Jing Chen | [@milantracy](https://github.com/milantracy) | Google
|
||||
Konstantin Bogomolov | [@konstantin-s-bogom](https://github.com/konstantin-s-bogom) | Google
|
||||
Jimmy Tran | [@trantoji](https://github.com/trantoji) | Google
|
||||
Anil Altinay | [@aaltinaydev](https://github.com/aaltinaydev) | Google
|
||||
Shailend Chand | [@shailend-g](https://github.com/shailend-g) | Google
|
||||
Parth Sarthi | [@parth-opensrc](https://github.com/parth-opensrc) | Google
|
||||
Ryan El Kochta | [@relkochta](https://github.com/relkochta) | Google
|
||||
Rex Ren | [@rexren-gif](https://github.com/rexren-gif) | Google
|
||||
Xin Zhong | [@xinzhong](https://github.com/xinzhong) | Google
|
||||
Caroline Zhu | [@carzh](https://github.com/carzh) | Google
|
||||
Alexander Cueva | [@kerumeto](https://github.com/kerumeto) | Google
|
||||
|
||||
## Emeritus Maintainers
|
||||
|
||||
Maintainers who have stepped back from active participation are recognized here.
|
||||
Emeritus maintainers do not have voting rights or merge access but are
|
||||
recognized for their contributions and may be consulted on project matters.
|
||||
|
||||
A maintainer is moved to emeritus status after 12 months of inactivity, or by
|
||||
request. An emeritus maintainer may return to active status through the normal
|
||||
maintainer nomination process.
|
||||
|
||||
Name | GitHub ID | Date moved to emeritus
|
||||
-------------------- | ---------------------------------------------------- | ----------------------
|
||||
Adin Scannell | [@amscanne](https://github.com/amscanne) | 2023-03-03
|
||||
Nicolas Lacasse | [@nlacasse](https://github.com/nlacasse) | 2025-06-27
|
||||
Bhasker Hariharan | [@hbhasker](https://github.com/hbhasker) | 2022-09-16
|
||||
Ian Gudger | [@iangudger](https://github.com/iangudger) | 2020-06-26
|
||||
Michael Pratt | [@prattmic](https://github.com/prattmic) | 2020-04-02
|
||||
Kevin Krakauer | [@kevinGC](https://github.com/kevinGC) | 2025-01-28
|
||||
Rahat Mahmood | [@mrahatm](https://github.com/mrahatm) | 2023-01-19
|
||||
Andrei Vagin | [@avagin](https://github.com/avagin) | 2026-02-12
|
||||
Tamir Duberstein | [@tamird](https://github.com/tamird) | 2022-07-12
|
||||
Ian Lewis | [@ianlewis](https://github.com/ianlewis) | 2023-07-17
|
||||
Ayush Ranjan | [@ayushr2](https://github.com/ayushr2) | 2026-06-24
|
||||
Ghanan Gowripalan | [@ghananigans](https://github.com/ghananigans) | 2023-10-16
|
||||
Shambhavi Srivastava | [@sshambhavi-252](https://github.com/sshambhavi-252) | 2023-10-10
|
||||
@@ -0,0 +1,6 @@
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
exports_files(["maintainers.yaml"])
|
||||
@@ -0,0 +1,166 @@
|
||||
# gVisor maintainer roster.
|
||||
#
|
||||
# Maintainer fields:
|
||||
# name: Full name or nickname.
|
||||
# github: GitHub username.
|
||||
# affiliation: Current employer or "Independent".
|
||||
# past_affiliation: Optional list of { affiliation: "...", until: "YYYY-MM-DD" } objects.
|
||||
# started: When the maintainer started contributing (YYYY-MM-DD). Sort by this.
|
||||
# status: One of:
|
||||
# - "ACTIVE" (does reviews, has merge permissions)
|
||||
# - "HIATUS_SINCE:YYYY-MM-DD" (doesn't do reviews, has permissions)
|
||||
# - "EMERITUS_SINCE:YYYY-MM-DD" (doesn't do reviews, no permissions)
|
||||
maintainers:
|
||||
- name: Adin Scannell
|
||||
github: amscanne
|
||||
affiliation: Google
|
||||
started: 2018-04-28
|
||||
status: EMERITUS_SINCE:2023-03-03
|
||||
- name: Fabricio Voznika
|
||||
github: fvoznika
|
||||
affiliation: Google
|
||||
started: 2018-04-28
|
||||
status: ACTIVE
|
||||
- name: Nicolas Lacasse
|
||||
github: nlacasse
|
||||
affiliation: Google
|
||||
started: 2018-04-28
|
||||
status: EMERITUS_SINCE:2025-06-27
|
||||
- name: Bhasker Hariharan
|
||||
github: hbhasker
|
||||
affiliation: Google
|
||||
started: 2018-05-01
|
||||
status: EMERITUS_SINCE:2022-09-16
|
||||
- name: Ian Gudger
|
||||
github: iangudger
|
||||
affiliation: Google
|
||||
started: 2018-05-01
|
||||
status: EMERITUS_SINCE:2020-06-26
|
||||
- name: Michael Pratt
|
||||
github: prattmic
|
||||
affiliation: Google
|
||||
started: 2018-05-01
|
||||
status: EMERITUS_SINCE:2020-04-02
|
||||
- name: Kevin Krakauer
|
||||
github: kevinGC
|
||||
affiliation: Google
|
||||
started: 2018-05-04
|
||||
status: EMERITUS_SINCE:2025-01-28
|
||||
- name: Jamie Liu
|
||||
github: nixprime
|
||||
affiliation: Google
|
||||
started: 2018-05-08
|
||||
status: HIATUS_SINCE:2026-07-17
|
||||
- name: Rahat Mahmood
|
||||
github: mrahatm
|
||||
affiliation: Google
|
||||
started: 2018-05-17
|
||||
status: EMERITUS_SINCE:2023-01-19
|
||||
- name: Andrei Vagin
|
||||
github: avagin
|
||||
affiliation: Google
|
||||
started: 2018-07-31
|
||||
status: EMERITUS_SINCE:2026-02-12
|
||||
- name: Tamir Duberstein
|
||||
github: tamird
|
||||
affiliation: Google
|
||||
started: 2018-08-25
|
||||
status: EMERITUS_SINCE:2022-07-12
|
||||
- name: Ian Lewis
|
||||
github: ianlewis
|
||||
affiliation: Google
|
||||
started: 2018-10-12
|
||||
status: EMERITUS_SINCE:2023-07-17
|
||||
- name: Zach Koopmans
|
||||
github: zkoopmans
|
||||
affiliation: Google
|
||||
started: 2018-11-26
|
||||
status: ACTIVE
|
||||
- name: Ayush Ranjan
|
||||
github: ayushr2
|
||||
affiliation: Modal
|
||||
past_affiliation:
|
||||
- affiliation: Google
|
||||
until: 2026-07-04
|
||||
started: 2019-05-23
|
||||
status: EMERITUS_SINCE:2026-06-24
|
||||
- name: Ghanan Gowripalan
|
||||
github: ghananigans
|
||||
affiliation: Google
|
||||
started: 2019-09-03
|
||||
status: EMERITUS_SINCE:2023-10-16
|
||||
- name: Nayana Bidari
|
||||
github: nybidari
|
||||
affiliation: Google
|
||||
started: 2020-01-09
|
||||
status: ACTIVE
|
||||
- name: Etienne Perot
|
||||
github: etienneperot
|
||||
affiliation: Google
|
||||
started: 2020-11-18
|
||||
status: ACTIVE
|
||||
- name: Lucas Manning
|
||||
github: manninglucas
|
||||
affiliation: Google
|
||||
started: 2021-06-29
|
||||
status: HIATUS_SINCE:2026-07-17
|
||||
- name: Jing Chen
|
||||
github: milantracy
|
||||
affiliation: Google
|
||||
started: 2021-10-20
|
||||
status: ACTIVE
|
||||
- name: Konstantin Bogomolov
|
||||
github: konstantin-s-bogom
|
||||
affiliation: Google
|
||||
started: 2022-02-28
|
||||
status: ACTIVE
|
||||
- name: Shambhavi Srivastava
|
||||
github: sshambhavi-252
|
||||
affiliation: Google
|
||||
started: 2022-04-01
|
||||
status: EMERITUS_SINCE:2023-10-10
|
||||
- name: Jimmy Tran
|
||||
github: trantoji
|
||||
affiliation: Google
|
||||
started: 2025-02-07
|
||||
status: ACTIVE
|
||||
- name: Anil Altinay
|
||||
github: aaltinaydev
|
||||
affiliation: Google
|
||||
started: 2025-08-05
|
||||
status: ACTIVE
|
||||
- name: Shailend Chand
|
||||
github: shailend-g
|
||||
affiliation: Google
|
||||
started: 2025-08-12
|
||||
status: ACTIVE
|
||||
- name: Parth Sarthi
|
||||
github: parth-opensrc
|
||||
affiliation: Google
|
||||
started: 2025-11-06
|
||||
status: ACTIVE
|
||||
- name: Ryan El Kochta
|
||||
github: relkochta
|
||||
affiliation: Google
|
||||
started: 2026-05-04
|
||||
status: ACTIVE
|
||||
- name: Rex Ren
|
||||
github: rexren-gif
|
||||
affiliation: Google
|
||||
started: 2026-05-07
|
||||
status: ACTIVE
|
||||
- name: Xin Zhong
|
||||
github: xinzhong
|
||||
affiliation: Google
|
||||
started: 2026-05-22
|
||||
status: ACTIVE
|
||||
- name: Caroline Zhu
|
||||
github: carzh
|
||||
affiliation: Google
|
||||
started: 2026-06-29
|
||||
status: ACTIVE
|
||||
- name: Alexander Cueva
|
||||
github: kerumeto
|
||||
affiliation: Google
|
||||
started: 2026-08-17
|
||||
status: ACTIVE
|
||||
@@ -0,0 +1,24 @@
|
||||
load("//tools:defs.bzl", "go_binary", "go_test")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "maintainers_gen",
|
||||
srcs = ["maintainers_gen.go"],
|
||||
deps = ["@in_gopkg_yaml_v3//:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "maintainers_test",
|
||||
srcs = ["maintainers_test.go"],
|
||||
data = [
|
||||
":maintainers_gen",
|
||||
"//:.github/reviewer.json",
|
||||
"//:MAINTAINERS.md",
|
||||
"//governance:maintainers.yaml",
|
||||
],
|
||||
deps = ["//pkg/test/testutil"],
|
||||
)
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2026 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Command maintainers_gen generates the contents of .github/reviewer.json and
|
||||
// MAINTAINERS.md from the maintainer roster in governance/maintainers.yaml.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
input = flag.String("input", "", "path to maintainers.yaml")
|
||||
format = flag.String("format", "reviewer.json", `output format: "reviewer.json" or "MAINTAINERS.md"`)
|
||||
output = flag.String("output", "", "path to write to; defaults to stdout")
|
||||
)
|
||||
|
||||
// pastAffiliation is a past employer of a maintainer.
|
||||
type pastAffiliation struct {
|
||||
Affiliation string `yaml:"affiliation"`
|
||||
Until string `yaml:"until"`
|
||||
}
|
||||
|
||||
// maintainer is a single entry of the maintainer roster.
|
||||
type maintainer struct {
|
||||
Name string `yaml:"name"`
|
||||
GitHub string `yaml:"github"`
|
||||
Affiliation string `yaml:"affiliation"`
|
||||
PastAffiliations []pastAffiliation `yaml:"past_affiliation"`
|
||||
Started string `yaml:"started"`
|
||||
Status string `yaml:"status"`
|
||||
}
|
||||
|
||||
// roster is the schema of maintainers.yaml.
|
||||
type roster struct {
|
||||
Maintainers []maintainer `yaml:"maintainers"`
|
||||
}
|
||||
|
||||
// parseStatus splits a status into its kind ("ACTIVE", "HIATUS_SINCE" or
|
||||
// "EMERITUS_SINCE") and date, and validates both.
|
||||
func parseStatus(status string) (string, string, error) {
|
||||
kind, date, hasDate := strings.Cut(status, ":")
|
||||
if hasDate {
|
||||
if _, err := time.Parse("2006-01-02", date); err != nil {
|
||||
return "", "", fmt.Errorf("invalid date in status %q", status)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case kind == "ACTIVE" && !hasDate:
|
||||
case kind == "HIATUS_SINCE" && hasDate:
|
||||
case kind == "EMERITUS_SINCE" && hasDate:
|
||||
default:
|
||||
return "", "", fmt.Errorf("invalid status %q", status)
|
||||
}
|
||||
return kind, date, nil
|
||||
}
|
||||
|
||||
// parseRoster parses and validates maintainers.yaml contents.
|
||||
func parseRoster(yamlData []byte) (*roster, error) {
|
||||
dec := yaml.NewDecoder(bytes.NewReader(yamlData))
|
||||
dec.KnownFields(true)
|
||||
var r roster
|
||||
if err := dec.Decode(&r); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse roster: %w", err)
|
||||
}
|
||||
seen := make(map[string]bool, len(r.Maintainers))
|
||||
var prev maintainer
|
||||
for _, m := range r.Maintainers {
|
||||
if m.Name == "" || m.GitHub == "" || m.Affiliation == "" || m.Started == "" || m.Status == "" {
|
||||
return nil, fmt.Errorf("maintainer %+v: name, github, affiliation, started and status are required", m)
|
||||
}
|
||||
if _, err := time.Parse("2006-01-02", m.Started); err != nil {
|
||||
return nil, fmt.Errorf("maintainer %q: invalid started date %q", m.GitHub, m.Started)
|
||||
}
|
||||
for _, p := range m.PastAffiliations {
|
||||
if p.Affiliation == "" {
|
||||
return nil, fmt.Errorf("maintainer %q: past_affiliation entries require an affiliation", m.GitHub)
|
||||
}
|
||||
if _, err := time.Parse("2006-01-02", p.Until); err != nil {
|
||||
return nil, fmt.Errorf("maintainer %q: invalid until date %q in past_affiliation", m.GitHub, p.Until)
|
||||
}
|
||||
}
|
||||
if _, _, err := parseStatus(m.Status); err != nil {
|
||||
return nil, fmt.Errorf("maintainer %q: %w", m.GitHub, err)
|
||||
}
|
||||
if m.Started < prev.Started {
|
||||
return nil, fmt.Errorf("maintainers are not sorted by started date: %q (%s) comes after %q (%s)", m.Name, m.Started, prev.Name, prev.Started)
|
||||
}
|
||||
prev = m
|
||||
if seen[m.GitHub] {
|
||||
return nil, fmt.Errorf("duplicate maintainer %q", m.GitHub)
|
||||
}
|
||||
seen[m.GitHub] = true
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// generateReviewerJSON renders `reviewer.json` contents.
|
||||
func generateReviewerJSON(r *roster) ([]byte, error) {
|
||||
reviewers := make(map[string]bool, len(r.Maintainers))
|
||||
for _, m := range r.Maintainers {
|
||||
kind, _, _ := parseStatus(m.Status)
|
||||
if kind == "EMERITUS_SINCE" {
|
||||
continue
|
||||
}
|
||||
reviewers[m.GitHub] = kind == "ACTIVE"
|
||||
}
|
||||
jsonData, err := json.MarshalIndent(reviewers, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot marshal reviewers: %w", err)
|
||||
}
|
||||
return append(jsonData, '\n'), nil
|
||||
}
|
||||
|
||||
// formatTable renders a markdown table with columns formatted.
|
||||
func formatTable(headers []string, rows [][]string) string {
|
||||
numCols := len(headers)
|
||||
colWidths := make([]int, numCols)
|
||||
for i, h := range headers {
|
||||
colWidths[i] = len(h)
|
||||
}
|
||||
for _, row := range rows {
|
||||
for i, cell := range row {
|
||||
if len(cell) > colWidths[i] {
|
||||
colWidths[i] = len(cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
var b strings.Builder
|
||||
// Header
|
||||
for i, h := range headers {
|
||||
if i > 0 {
|
||||
b.WriteString(" | ")
|
||||
}
|
||||
if i == numCols-1 {
|
||||
b.WriteString(h)
|
||||
} else {
|
||||
b.WriteString(h)
|
||||
b.WriteString(strings.Repeat(" ", colWidths[i]-len(h)))
|
||||
}
|
||||
}
|
||||
b.WriteString("\n")
|
||||
// Separator
|
||||
for i := 0; i < numCols; i++ {
|
||||
if i > 0 {
|
||||
b.WriteString(" | ")
|
||||
}
|
||||
b.WriteString(strings.Repeat("-", colWidths[i]))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
// Rows
|
||||
for _, row := range rows {
|
||||
for i, cell := range row {
|
||||
if i > 0 {
|
||||
b.WriteString(" | ")
|
||||
}
|
||||
if i == numCols-1 {
|
||||
b.WriteString(cell)
|
||||
} else {
|
||||
b.WriteString(cell)
|
||||
b.WriteString(strings.Repeat(" ", colWidths[i]-len(cell)))
|
||||
}
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// generateMaintainersMD renders `MAINTAINERS.md` contents.
|
||||
func generateMaintainersMD(r *roster) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("<!-- Generated from governance/maintainers.yaml by\n")
|
||||
b.WriteString(" //governance/tools/maintainers:maintainers_gen; do not edit directly. -->\n\n")
|
||||
b.WriteString("The current maintainers of the gVisor project are listed below.\n\n")
|
||||
|
||||
var activeRows [][]string
|
||||
for _, m := range r.Maintainers {
|
||||
if kind, _, _ := parseStatus(m.Status); kind != "EMERITUS_SINCE" {
|
||||
activeRows = append(activeRows, []string{
|
||||
m.Name,
|
||||
fmt.Sprintf("[@%s](https://github.com/%s)", m.GitHub, m.GitHub),
|
||||
m.Affiliation,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.WriteString(formatTable([]string{"Name", "GitHub ID", "Company/Organization"}, activeRows))
|
||||
|
||||
b.WriteString(`
|
||||
## Emeritus Maintainers
|
||||
|
||||
Maintainers who have stepped back from active participation are recognized here.
|
||||
Emeritus maintainers do not have voting rights or merge access but are
|
||||
recognized for their contributions and may be consulted on project matters.
|
||||
|
||||
A maintainer is moved to emeritus status after 12 months of inactivity, or by
|
||||
request. An emeritus maintainer may return to active status through the normal
|
||||
maintainer nomination process.
|
||||
|
||||
`)
|
||||
|
||||
var emeritusRows [][]string
|
||||
for _, m := range r.Maintainers {
|
||||
if kind, date, _ := parseStatus(m.Status); kind == "EMERITUS_SINCE" {
|
||||
emeritusRows = append(emeritusRows, []string{
|
||||
m.Name,
|
||||
fmt.Sprintf("[@%s](https://github.com/%s)", m.GitHub, m.GitHub),
|
||||
date,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.WriteString(formatTable([]string{"Name", "GitHub ID", "Date moved to emeritus"}, emeritusRows))
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *input == "" {
|
||||
fmt.Fprintln(os.Stderr, "must specify -input")
|
||||
os.Exit(1)
|
||||
}
|
||||
yamlData, err := os.ReadFile(*input)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot read roster: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
r, err := parseRoster(yamlData)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot parse %s: %v\n", *input, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var outData []byte
|
||||
switch *format {
|
||||
case "reviewer.json":
|
||||
outData, err = generateReviewerJSON(r)
|
||||
case "MAINTAINERS.md":
|
||||
outData, err = generateMaintainersMD(r)
|
||||
default:
|
||||
err = fmt.Errorf("invalid format %q", *format)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot generate output: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *output == "" {
|
||||
os.Stdout.Write(outData)
|
||||
} else if err := os.WriteFile(*output, outData, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot write %s: %v\n", *output, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maintainers_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/test/testutil"
|
||||
)
|
||||
|
||||
// checkInSync verifies that the checked-in file at path matches what
|
||||
// `maintainers_gen` generates.
|
||||
func checkInSync(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
format := filepath.Base(path)
|
||||
genBin, err := testutil.FindFile("governance/tools/maintainers/maintainers_gen")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot find maintainers_gen: %v", err)
|
||||
}
|
||||
yamlPath, err := testutil.FindFile("governance/maintainers.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot find maintainers.yaml: %v", err)
|
||||
}
|
||||
wantPath, err := testutil.FindFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot find %s: %v", path, err)
|
||||
}
|
||||
cmd := exec.Command(genBin, "-input", yamlPath, "-format", format)
|
||||
cmd.Stderr = os.Stderr
|
||||
got, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cannot run maintainers_gen: %v", err)
|
||||
}
|
||||
want, err := os.ReadFile(wantPath)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read %s: %v", path, err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("%s is out of sync with governance/maintainers.yaml; regenerate with:\n"+
|
||||
" make run TARGETS=//governance/tools/maintainers:maintainers_gen ARGS=\"-input governance/maintainers.yaml -format %s -output %s\"\n"+
|
||||
"got:\n%swant:\n%s", path, format, path, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReviewerJSONInSync(t *testing.T) {
|
||||
checkInSync(t, ".github/reviewer.json")
|
||||
}
|
||||
|
||||
func TestMaintainersMDInSync(t *testing.T) {
|
||||
checkInSync(t, "MAINTAINERS.md")
|
||||
}
|
||||
Reference in New Issue
Block a user