governance

package module
v0.0.0-...-8c39cb6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 31, 2024 License: Apache-2.0 Imports: 12 Imported by: 0

README

Concourse Governance

This document outlines a set of policies in order to provide a level playing field and open process for contributors to join the Concourse project.

In addition to this document, this repository contains live configuration for the state of the Concourse GitHub organization. All configuration is automatically applied via Terraform and synchronized daily to prevent drift.

Governance Model

Individual contributors to the Concourse project can become members of teams, each with a stated purpose, a clear set of responsibilities, and a list of repos that they maintain.

Teams collaborate through discussions on GitHub and propose changes through pull requests that may cross team boundaries.

Ideally, teams should be split along boundaries that enhance the focus given to different facets of the Concourse project. Repositories should typically belong to a single team in order to encourage advocacy for different facets through collaboration.

For example:

  • the core team has authority over the RFC process and associated design principles, but cannot directly push to the the Concourse repo.
  • the maintainers team has authority over the Concourse repo and submits RFCs to develop a roadmap that aligns with Concourse's core design principles.
  • the core team engages with the maintainers team to ensure new proposals do not introduce unnecessary risk or become a maintenance burden.
  • the maintainers team then guides the planning and implementation of the proposal through pull requests to the Concourse repo.

Teams may split off from larger teams as more of these boundaries are discovered. Careful attention should be paid to teams with too many responsibilities - there may be significant facets being neglected.

Individual Contributors

Individual contributors are listed under ./contributors. Pull requests will be reviewed by members of the community team. Feel free to submit one at any time!

The name of the file should match your github handle. Each ./contributors/*.yml file has the following fields:

  • name - the contributor's real name, or an alias if they would rather not share.
  • github - the contributor's GitHub login
  • discord - the contributor's Discord username + number, e.g. foo#123
  • repos - map from repo name to permission to grant for the user. this should only be used for bot accounts; in general repo permissions should be done through teams.

Each contributor will be granted membership of the Concourse GitHub organization. This does not grant much on its own; repository access for example is determined through teams.

Note: the Discord attribute is not used at the moment, but it may be helpful in the future to have someplace that correlates these different identities.

Teams

Teams are listed under ./teams. Pull requests will be reviewed by the community team, who will further request reviews from all affected teams or individuals. (This can probably be automated at some point.)

Each ./teams/*.yml file has the following fields:

  • name - a name for the team, stylized in lowercase.
  • purpose - a brief description of the team's focus.
  • responsibilities - a list of the team's discrete responsibilities, or a link to where they can be found.
  • members - a list of contributors to add to the team, e.g. foo for ./contributors/foo.yml.
  • repos - a list of GitHub repositories for the team to be added to.

Each team must have a stated purpose summarizing its goals.

Each team is also responsible for maintaining a list of its responsibilities. (No need to list that one.) Doing so clarifies the scope of a team for newcomers and makes it easier to tell when a team is overloaded and could benefit from being divided or reorganized.

Each team lists its members which correspond to filenames under ./contributors (without the .yml).

Each team lists GitHub repositories for which the team will be granted the Maintain permission.

Each team is responsible for determining the best way for the team to operate, though it is strongly encouraged that each team work in the open, either on GitHub or somewhere easy to access, to the extent that doing so is beneficial to the team and to the community. (For example, teams may choose to use a private discussion area to handle sensitive matters.)

Suggestion: team processes can be defined in a new repository managed exclusively by the team. The team repository can be created via submitting a PR to this repo. See Repositories.

Voting

Decisions are reached through consensus among the team members through a 66%+ supermajority unless stated otherwise through the team's own processes. (Implementation of said process would require a 66% supermajority.)

Voting can be expressed through pull request review, leaving a comment, or through some other form of record - ideally permanent.

Teams are not required to have designated leaders. Teams may choose to designate a leader and define their role and responsibilities through a vote amongst the team.

Joining a Team

To propose the addition of a team member (either yourself or on behalf of someone else), submit a PR that adds them as a contributor (if needed) and lists them as a member of the desired team.

Pull requests that add someone to a team require enough approving reviewers to pass the voting process. The community team member reviewing the pull request is responsible for determining the number of required votes based on the destination team's size and voting process, and they will merge the PR when the necessary votes have been acquired, or close the PR if the necessary votes cannot be reached. (Note: there's room for human error here; ideally this would be automated, but until then please assist by leaving a comment if anything is wrong.)

There are no specific qualifications for joining a team outlined by the governance model itself; gaining an approving vote may be entirely subjective and the barrier to entry will vary from team to team. As a general rule, applications with no prior context or trust to build upon will almost certainly be rejected.

Leaving a team

A team member may choose to leave their team at any time by submitting a PR that removes themself from the list of members. A vote is not necessary for voluntarily leaving a team.

To remove someone else from the team, submit a PR as above and it will go through the same voting process as joining. The member being removed may also vote.

Creating a new Team

New teams may be formed at any time by submitting a PR. A team with only one member is probably not a good sign, so try to recruit folks during this stage.

If a new team is being created to split off from a larger team, you will have to negotiate ownership of the relevant repos and ideally move them entirely to the new team. This will obviously require approval from the original team.

Repositories

Repositories are listed under ./repos. Pull requests will be reviewed by the infrastructure team.

Each ./repos/*.yml file has the following fields:

  • name - a name for the repository.
  • description - a description for the repository.
  • topics - topics to set for the repository.
  • homepage_url - a website (if any) associated to the repository.
  • has_issues - whether the repository has Issues enabled (default false).
  • has_projects - whether the repository has Projects enabled (default false).
  • has_wiki - whether the repository has the Wiki enabled (default false).
  • has_discussions - whether the repository has the Discussions enabled (default false).
  • pages - GitHub pages configuration:
    • branch - the branch to build.
    • path - the path to serve (default /).
    • cname - an optional CNAME to set for the website.
  • branch_protection - a list of branch protection settings:
    • pattern - branch name pattern to match.
    • allows_deletions - whether the branches can be deleted.
    • required_checks - required status checks for PRs to be merged.
    • strict_checks - require branches to be up-to-date before merging.
    • required_reviews - number of approved reviews required for PRs to be merged.
    • dismiss_stale_reviews - dismiss reviews when new commits are pushed.
    • require_code_owner_reviews - require approval from code owners for PRs which affect files with designated owners.
  • deploy_keys - a list of deploy keys to add to the repo
    • title - a title for the key
    • public_key - the public key
    • writable - whether the key can push to the repo

All repositories have vulnerability alerts enabled.

All repositories are configured to delete branches once their PR is merged.

All repositories will be archived upon deletion from this repo (instead of being deleted). Permanent deletion must be done manually by a member of the infrastructure team.

Amending the Governance Model

Frankly, I am more used to solving computer problems than human problems, so this process may be naive, it may feel too rigid, or it may feel completely ambiguous. Nothing here is set in stone. Please improve it as necessary and remove this disclaimer once we feel more confident. - @vito

Pull requests to this process (README.md) will be reviewed by the core team.

Enforcing the Governance Model

The configuration in this repository is applied automatically via Terraform.

In addition to the Terraform configuration, the state of the entire GitHub organization can be tested against the desired state via go test. This test suite will also detect any 'extra' configuration like untracked repositories, unknown teams, and extra repository collaborators.

Applying Changes

To apply these changes you must be an Owner of the Concourse GitHub organization.

Set the github_token var and run terraform apply:

$ terraform init # once
$ echo '{"github_token":"..."}' > .auto.tfvars.json
$ terraform apply

This token must have admin:org and repo scopes.

Testing Actual vs. Desired State

Tests are included which will verify that all permissions in the relevant services reflect the configuration in the repository.

Running the tests requires a $GITHUB_TOKEN to be set.

$ export GITHUB_TOKEN="$(jq -r .github_token .auto.tfvars.json)"
$ go test

Test failures must be addressed immediately as they may indicate abuse, though laziness or ignorance of this process is more likely.

GitHub Organization Settings

This governance model requires that organization members have extremely limited privileges. Unfortunately these can't currently be set by Terraform, so I'm documenting them here for good measure.

The following settings are required for any of this to make sense:

  • Base permissions must be "None" so that organization membership does not grant visibility of private repositories (if any exist) and repository access is determined exclusively through teams.
  • Repository creation and Pages creation must be disabled for both Public and Private so that all repository management shall be done through this repo.
  • Allow members to create teams must be disabled so that all team administration shall be done through this repo.

Additionally, repository admin permissions should be restricted. No team will ever be an 'admin' at the repo level, so this should never come up, but we can prevent further damage if someone does manage to escalate:

  • Allow members to change repository visibilities for this organization should be disabled.
  • Allow members to delete or transfer repositories for this organization should be disabled.
  • Allow members to delete issues for this organization should be disabled.

These settings probably won't have much impact:

  • Allow forking of private repositories should be disabled just to keep access tidy.
  • Allow users with read access to create discussions is confusingly under the 'Admin repository permissions' heading but sounds rather innocuous, so it can be left checked.

More settings may appear on the member privileges page at some point. Please update the above listing if/when this does occur.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DiscordPermissions = map[string]int64{
	"CREATE_INSTANT_INVITE": 0x00000001,
	"KICK_MEMBERS":          0x00000002,
	"BAN_MEMBERS":           0x00000004,
	"ADMINISTRATOR":         0x00000008,
	"MANAGE_CHANNELS":       0x00000010,
	"MANAGE_SERVER":         0x00000020,
	"ADD_REACTIONS":         0x00000040,
	"VIEW_AUDIT_LOG":        0x00000080,
	"PRIORITY_SPEAKER":      0x00000100,
	"STREAM":                0x00000200,
	"VIEW_CHANNEL":          0x00000400,
	"SEND_MESSAGES":         0x00000800,
	"SEND_TTS_MESSAGES":     0x00001000,
	"MANAGE_MESSAGES":       0x00002000,
	"EMBED_LINKS":           0x00004000,
	"ATTACH_FILES":          0x00008000,
	"READ_MESSAGE_HISTORY":  0x00010000,
	"MENTION_EVERYONE":      0x00020000,
	"USE_EXTERNAL_EMOJIS":   0x00040000,
	"VIEW_SERVER_INSIGHTS":  0x00080000,
	"CONNECT":               0x00100000,
	"SPEAK":                 0x00200000,
	"MUTE_MEMBERS":          0x00400000,
	"DEAFEN_MEMBERS":        0x00800000,
	"MOVE_MEMBERS":          0x01000000,
	"USE_VAD":               0x02000000,
	"CHANGE_NICKNAME":       0x04000000,
	"MANAGE_NICKNAMES":      0x08000000,
	"MANAGE_ROLES":          0x10000000,
	"MANAGE_WEBHOOKS":       0x20000000,
	"MANAGE_EMOJIS":         0x40000000,
}

1. copied from https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags 2. replaced GUILD with SERVER

View Source
var TeamRoleBasePermissions = DiscordPermissionSet{
	"VIEW_CHANNEL",
	"CREATE_INSTANT_INVITE",
	"CHANGE_NICKNAME",
	"SEND_MESSAGES",
	"EMBED_LINKS",
	"ATTACH_FILES",
	"ADD_REACTIONS",
	"USE_EXTERNAL_EMOJIS",
	"MENTION_EVERYONE",
	"READ_MESSAGE_HISTORY",
	"SEND_TTS_MESSAGES",
	"CONNECT",
	"SPEAK",
	"STREAM",
	"USE_VAD",
}

Functions

This section is empty.

Types

type Config

type Config struct {
	Contributors map[string]Person
	Teams        map[string]Team
	Repos        map[string]Repo
}

func LoadConfig

func LoadConfig(tree fs.FS) (*Config, error)

func (*Config) DesiredGitHubState

func (cfg *Config) DesiredGitHubState() GitHubState

func (*Config) DesiredMailgunState

func (config *Config) DesiredMailgunState(domain string) *MailgunState

type Discord

type Discord struct {
	Role     string `yaml:"role,omitempty"`
	Color    int    `yaml:"color,omitempty"`
	Priority int    `yaml:"priority,omitempty"`

	AddedPermissions DiscordPermissionSet `yaml:"added_permissions,omitempty"`

	// if set, the role will never be removed. this is primarily to
	// grandfather in users who have roles which predated the governance
	// automation, i.e. the 'contributors' role.
	Sticky bool `yaml:"sticky,omitempty"`
}

type DiscordPermissionSet

type DiscordPermissionSet []string

defaults copied from newly created role; may be worth tuning later

func (DiscordPermissionSet) Permissions

func (set DiscordPermissionSet) Permissions() (int64, error)

type GitHubDeployKey

type GitHubDeployKey struct {
	Title    string
	Key      string
	ReadOnly bool
}

type GitHubOrgMember

type GitHubOrgMember struct {
	Name  string
	Login string
	Role  OrgRole
}

type GitHubRepo

type GitHubRepo struct {
	Name        string
	Description string
	IsPrivate   bool
	Topics      []string
	HomepageURL string
	HasIssues   bool
	HasWiki     bool
	HasProjects bool

	DirectCollaborators []GitHubRepoCollaborator

	BranchProtectionRules []GitHubRepoBranchProtectionRule

	DeployKeys []GitHubDeployKey
}

func (GitHubRepo) Collaborator

func (repo GitHubRepo) Collaborator(login string) (GitHubRepoCollaborator, bool)

type GitHubRepoBranchProtectionRule

type GitHubRepoBranchProtectionRule struct {
	Pattern string

	IsAdminEnforced bool

	AllowsDeletions   bool
	AllowsForcePushes bool

	RequiresStatusChecks        bool
	RequiresStrictStatusChecks  bool
	RequiredStatusCheckContexts []string

	RestrictsPushes bool

	RequiresLinearHistory bool

	RequiresCommitSignatures bool

	RequiresApprovingReviews     bool
	RequiredApprovingReviewCount int
	DismissesStaleReviews        bool
	RequiresCodeOwnerReviews     bool
	RestrictsReviewDismissals    bool
}

type GitHubRepoCollaborator

type GitHubRepoCollaborator struct {
	Login      string
	Permission RepoPermission
}

type GitHubState

type GitHubState struct {
	Organization string

	Members []GitHubOrgMember
	Teams   []GitHubTeam
	Repos   []GitHubRepo
}

func LoadGitHubState

func LoadGitHubState(orgName string) (*GitHubState, error)

func (*GitHubState) LoadMembers

func (state *GitHubState) LoadMembers(ctx context.Context, client *githubv4.Client) error

func (*GitHubState) LoadRepos

func (state *GitHubState) LoadRepos(ctx context.Context, client *githubv4.Client) error

func (*GitHubState) LoadTeams

func (state *GitHubState) LoadTeams(ctx context.Context, client *githubv4.Client) error

func (GitHubState) Member

func (state GitHubState) Member(login string) (GitHubOrgMember, bool)

func (GitHubState) Repo

func (state GitHubState) Repo(name string) (GitHubRepo, bool)

func (GitHubState) Team

func (state GitHubState) Team(name string) (GitHubTeam, bool)

type GitHubTeam

type GitHubTeam struct {
	ID          int
	Name        string
	Description string
	Members     []GitHubTeamMember
	Repos       []GitHubTeamRepoAccess
}

func (GitHubTeam) Member

func (team GitHubTeam) Member(login string) (GitHubTeamMember, bool)

func (GitHubTeam) Repo

func (team GitHubTeam) Repo(name string) (GitHubTeamRepoAccess, bool)

type GitHubTeamMember

type GitHubTeamMember struct {
	Login string
	Role  TeamRole
}

type GitHubTeamRepoAccess

type GitHubTeamRepoAccess struct {
	Name       string
	Permission RepoPermission
}

type MailgunRoute

type MailgunRoute struct {
	ID          string
	Description string
	Expression  string
	Actions     []string
}

type MailgunState

type MailgunState struct {
	Routes []MailgunRoute
}

func LoadMailgunState

func LoadMailgunState(domain string) (*MailgunState, error)

type OrgRole

type OrgRole string
const OrgRoleAdmin OrgRole = "ADMIN"
const OrgRoleMember OrgRole = "MEMBER"

type Person

type Person struct {
	Name    string            `yaml:"name"`
	GitHub  string            `yaml:"github"`
	Discord string            `yaml:"discord,omitempty"`
	Email   string            `yaml:"email,omitempty"`
	Repos   map[string]string `yaml:"repos,omitempty"`
}

type Repo

type Repo struct {
	Name           string   `yaml:"name"`
	Description    string   `yaml:"description"`
	Private        bool     `yaml:"private,omitempty"`
	Topics         []string `yaml:"topics,omitempty"`
	HomepageURL    string   `yaml:"homepage_url,omitempty"`
	HasIssues      bool     `yaml:"has_issues,omitempty"`
	HasProjects    bool     `yaml:"has_projects,omitempty"`
	HasWiki        bool     `yaml:"has_wiki,omitempty"`
	HasDiscussions bool     `yaml:"has_discussions,omitempty"`

	Pages *RepoPages `yaml:"pages,omitempty"`

	BranchProtection []RepoBranchProtection `yaml:"branch_protection,omitempty"`

	Labels []RepoLabel `yaml:"labels,omitempty"`

	DeployKeys []RepoDeployKey `yaml:"deploy_keys"`
}

type RepoBranchProtection

type RepoBranchProtection struct {
	Pattern string `yaml:"pattern"`

	AllowsDeletions bool `yaml:"allows_deletions,omitempty"`

	RequiredChecks []string `yaml:"required_checks,omitempty"`
	StrictChecks   bool     `yaml:"strict_checks,omitempty"`

	RequiredReviews         int  `yaml:"required_reviews,omitempty"`
	DismissStaleReviews     bool `yaml:"dismiss_stale_reviews,omitempty"`
	RequireCodeOwnerReviews bool `yaml:"require_code_owner_reviews,omitempty"`
}

type RepoDeployKey

type RepoDeployKey struct {
	Title     string `yaml:"title"`
	PublicKey string `yaml:"public_key"`
	Writable  bool   `yaml:"writable"`
}

type RepoLabel

type RepoLabel struct {
	Name  string `yaml:"name"`
	Color int    `yaml:"color"`
}

type RepoPages

type RepoPages struct {
	CNAME  string `yaml:"cname,omitempty"`
	Branch string `yaml:"branch"`
	Path   string `yaml:"path,omitempty"`
}

type RepoPermission

type RepoPermission string
const RepoPermissionAdmin RepoPermission = "ADMIN"
const RepoPermissionMaintain RepoPermission = "MAINTAIN"
const RepoPermissionRead RepoPermission = "READ"
const RepoPermissionTriage RepoPermission = "TRIAGE"
const RepoPermissionWrite RepoPermission = "WRITE"

type Team

type Team struct {
	Name             string   `yaml:"name"`
	Purpose          string   `yaml:"purpose"`
	Responsibilities []string `yaml:"responsibilities"`

	Discord Discord `yaml:"discord,omitempty"`

	AllContributors bool     `yaml:"all_contributors"`
	RawMembers      []string `yaml:"members"`

	RequiresEmail bool `yaml:"requires_email,omitempty"`

	RawRepoPermission string   `yaml:"repo_permission"`
	Repos             []string `yaml:"repos,omitempty"`
}

func (Team) Members

func (team Team) Members(cfg *Config) map[string]Person

func (Team) RepoPermission

func (team Team) RepoPermission() RepoPermission

type TeamRole

type TeamRole string
const TeamRoleMaintainer TeamRole = "MAINTAINER"
const TeamRoleMember TeamRole = "MEMBER"

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL