Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ These environment variables are required to work on the relevant cog.
| Environment | Relevant cog | Description | Required/Default |
| ------------------------------------- | --------------------- | ------------------------------------------------------------------------- | ------------------------- |
| KING_ARTHUR_CLOUDFLARE_TOKEN | Zones | A token for the Cloudflare API used for the Cloudflare commands in Arthur | Required |
| KING_ARTHUR_GITHUB_ORG | GrafanaGitHubTeamSync | The github organisation to fetch teams from | python-discord |
| KING_ARTHUR_GITHUB_TOKEN | GrafanaGitHubTeamSync | The github token used to fetch teams to populate grafana | Required |
| KING_ARTHUR_GRAFANA_URL | GrafanaGitHubTeamSync | The URL to the grafana instance to manage teams | https://grafana.pydis.wtf |
| KING_ARTHUR_GRAFANA_TOKEN | GrafanaGitHubTeamSync | The grafana token used to sync teams with github | Required |
| KING_ARTHUR_GITHUB_ORG | GitHubManagement | The github organisation to fetch teams from | python-discord |
| KING_ARTHUR_GITHUB_TOKEN | GitHubManagement | The github token used to manage the GitHub organisation | Required |
| KING_ARTHUR_GITHUB_TEAM | GitHubManagement | The slug of the GitHub team to add new members to | staff |
| KING_ARTHUR_GRAFANA_URL | GrafanaLDAPTeamSync | The URL to the grafana instance to manage teams | https://grafana.pydis.wtf |
| KING_ARTHUR_GRAFANA_TOKEN | GrafanaLDAPTeamSync | The grafana token used to sync teams with LDAP | Required |
| KING_ARTHUR_YOUTUBE_API_KEY | Motivation | The YouTube API key to fetch missions with | Required |

### LDAP & Directory integrations
Expand Down
7 changes: 2 additions & 5 deletions arthur/apis/github/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from .teams import GithubTeamNotFoundError, list_team_members
from .teams import GitHubError, add_staff_member

__all__ = (
"GithubTeamNotFoundError",
"list_team_members",
)
__all__ = ("GitHubError", "add_staff_member")
57 changes: 34 additions & 23 deletions arthur/apis/github/teams.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
import aiohttp

from arthur.config import CONFIG
from arthur.log import logger

HEADERS = {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"Authorization": f"Bearer {CONFIG.github_token.get_secret_value()}",
}
BASE_URL = "https://api.github.com"
MEMBERS_PER_PAGE = 100


class GithubTeamNotFoundError(aiohttp.ClientResponseError):
"""Raised when a github team could not be found."""


async def list_team_members(team_slug: str, session: aiohttp.ClientSession) -> list[dict[str, str]]:
"""List all Github teams."""
endpoint = f"{BASE_URL}/orgs/{CONFIG.github_org}/teams/{team_slug}/members"
params = {"per_page": MEMBERS_PER_PAGE}
async with session.get(endpoint, headers=HEADERS, params=params) as response:
response.raise_for_status()
teams_resp = await response.json()
if len(teams_resp) == MEMBERS_PER_PAGE:
logger.warning(
"Max number (%d) of members returned when fetching members of %s. Some members may have been missed.",
MEMBERS_PER_PAGE,
team_slug,
)
return teams_resp

HTTP_404 = 404
HTTP_403 = 403
HTTP_422 = 422


class GitHubError(Exception):
"""Custom exception for GitHub API errors."""

def __init__(self, message: str):
super().__init__(message)


async def add_staff_member(username: str) -> None:
"""Add a user to the default GitHub team."""
async with aiohttp.ClientSession() as session:
endpoint = f"https://api.github.com/orgs/{CONFIG.github_org}/teams/{CONFIG.github_team}/memberships/{username}"
async with session.put(endpoint, headers=HEADERS) as response:
try:
response.raise_for_status()
return await response.json()
except aiohttp.ClientResponseError as e:
if e.status == HTTP_404:
msg = f"Team or user not found: {e.message}"
raise GitHubError(msg)
if e.status == HTTP_403:
msg = f"Forbidden: {e.message}"
raise GitHubError(msg)
if e.status == HTTP_422:
msg = "Cannot add organisation as a team member"
raise GitHubError(msg)

msg = f"Unexpected error: {e.message}"
raise GitHubError(msg)
6 changes: 5 additions & 1 deletion arthur/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, *args: list[Any], **kwargs: dict[str, Any]) -> None:
super().__init__(*args, **kwargs)
self.add_check(self._is_devops)

async def _is_devops(self, ctx: commands.Context | Interaction) -> bool:
async def _is_devops(self, ctx: commands.Context | Interaction) -> bool: # noqa: PLR0911
"""Check all commands are executed by authorised personnel."""
u = ctx.user if isinstance(ctx, Interaction) else ctx.author
if await arthur.instance.is_owner(u):
Expand All @@ -38,6 +38,10 @@ async def _is_devops(self, ctx: commands.Context | Interaction) -> bool:
if ctx.command.name in {"ed", "rules", "monitor"}:
return True

if ctx.command.cog_name == "GitHubManagement":
# Commands in this cog have explicit additional checks.
return True

if not ctx.guild:
return False

Expand Down
2 changes: 2 additions & 0 deletions arthur/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ class Config(
grafana_token: pydantic.SecretStr | None = None
github_token: pydantic.SecretStr | None = None
github_org: str = "python-discord"
github_team: str = "staff"

devops_role: int = 409416496733880320
helpers_role: int = 267630620367257601
admins_role: int = 267628507062992896
guild_id: int = 267624335836053506
devops_channel_id: int = 675756741417369640
devops_vc_id: int = 881573757536329758
Expand Down
1 change: 1 addition & 0 deletions arthur/exts/github/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Utilities for managing the GitHub organisation."""
42 changes: 42 additions & 0 deletions arthur/exts/github/management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Commands for managing the GitHub organisation and teams."""

from discord.ext.commands import Cog, Context, group

from arthur.apis.github import GitHubException, add_staff_member
from arthur.bot import KingArthur
from arthur.config import CONFIG


class GitHubManagement(Cog):
"""Ed is the standard text editor."""

def __init__(self, bot: KingArthur) -> None:
self.bot = bot

async def cog_check(self, ctx: Context) -> bool:
"""Check if the user has permission to use this cog."""
return (
CONFIG.admins_role in [r.id for r in ctx.author.roles]
or CONFIG.devops_role in [r.id for r in ctx.author.roles]
or await self.bot.is_owner(ctx.author)
)

@group(name="github", invoke_without_command=True)
async def github(self, ctx: Context) -> None:
"""Group of commands for managing the GitHub organisation."""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)

@github.command(name="add")
async def add_team_member(self, ctx: Context, username: str) -> None:
"""Add a user to the default GitHub team."""
try:
await add_staff_member(username)
await ctx.send(f":white_check_mark: Successfully invited {username} to the staff team.")
except GitHubException as e:
await ctx.send(f":x: Failed to add {username} to the staff team: {e}")


async def setup(bot: KingArthur) -> None:
"""Add cog to bot."""
await bot.add_cog(GitHubManagement(bot))
172 changes: 0 additions & 172 deletions arthur/exts/grafana/github_team_sync.py

This file was deleted.

Loading