aboutsummaryrefslogtreecommitdiffstats
path: root/arthur/exts/grafana/github_team_sync.py
diff options
context:
space:
mode:
Diffstat (limited to 'arthur/exts/grafana/github_team_sync.py')
-rw-r--r--arthur/exts/grafana/github_team_sync.py159
1 files changed, 159 insertions, 0 deletions
diff --git a/arthur/exts/grafana/github_team_sync.py b/arthur/exts/grafana/github_team_sync.py
new file mode 100644
index 0000000..873c6c4
--- /dev/null
+++ b/arthur/exts/grafana/github_team_sync.py
@@ -0,0 +1,159 @@
+import aiohttp
+import discord
+from discord.ext import commands, tasks
+
+from arthur.apis import github, grafana
+from arthur.bot import KingArthur
+from arthur.log import logger
+
+from . import MissingMembers, SyncFigures
+
+
+class GrafanaGitHubTeamSync(commands.Cog):
+ """
+ Update Grafana team membership to match Github team membership.
+
+ Grafana team name must match Github team slug exactly.
+ Use `gh api orgs/{org-name}/teams` to get a list of teams in an org
+ """
+
+ def __init__(self, bot: KingArthur) -> None:
+ self.bot = bot
+ self.sync_github_grafana_teams.start()
+
+ async def _add_missing_members(
+ self,
+ grafana_team_id: int,
+ github_team_members: set[str],
+ grafana_team_members: set[str],
+ all_grafana_users: list[dict],
+ ) -> MissingMembers:
+ """
+ Adds members to the Grafana team if they're in the Github team and not already present.
+
+ Returns the number of missing members, and the number of members it could actually add.
+ """
+ missing_members = github_team_members - grafana_team_members
+ added_members = 0
+ for grafana_user in all_grafana_users:
+ if grafana_user["login"] not in missing_members:
+ continue
+ if grafana_user.get("auth_module") != "oauth_github":
+ continue
+
+ await grafana.add_user_to_team(
+ grafana_user["userId"],
+ grafana_team_id,
+ self.bot.http_session,
+ )
+ added_members += 1
+ return MissingMembers(count=len(missing_members), successfully_added=added_members)
+
+ async def _remove_extra_members(
+ self,
+ grafana_team_id: int,
+ github_team_members: set[str],
+ grafana_team_members: set[str],
+ all_grafana_users: list[dict],
+ ) -> int:
+ """
+ Removes Grafana users from a team if they are not present in the Github team.
+
+ Return how many were removed.
+ """
+ extra_members = grafana_team_members - github_team_members
+ removed_members = 0
+ for grafana_user in all_grafana_users:
+ if grafana_user["login"] not in extra_members:
+ continue
+ await grafana.remove_user_from_team(
+ grafana_user["userId"],
+ grafana_team_id,
+ self.bot.http_session,
+ )
+ removed_members += 1
+ return removed_members
+
+ async def _sync_teams(self, team: dict[str, str]) -> SyncFigures:
+ """
+ Ensure members in Github are present in Grafana teams.
+
+ Return the number of members missing from the Grafana team, and the number of members added.
+ """
+ github_team_members = {
+ member["login"]
+ for member in await github.list_team_members(team["name"], self.bot.http_session)
+ }
+ grafana_team_members = {
+ member["login"]
+ for member in await grafana.list_team_members(team["id"], self.bot.http_session)
+ if member.get("auth_module") == "oauth_github"
+ }
+
+ all_grafana_users = await grafana.get_all_users(self.bot.http_session)
+ added_members = await self._add_missing_members(
+ team["id"],
+ github_team_members,
+ grafana_team_members,
+ all_grafana_users,
+ )
+ removed_members = await self._remove_extra_members(
+ team["id"],
+ github_team_members,
+ grafana_team_members,
+ all_grafana_users,
+ )
+
+ return SyncFigures(added=added_members, removed=removed_members)
+
+ @tasks.loop(hours=12)
+ async def sync_github_grafana_teams(self, channel: discord.TextChannel | None = None) -> None:
+ """Update Grafana team membership to match Github team membership."""
+ grafana_teams = await grafana.list_teams(self.bot.http_session)
+ embed = discord.Embed(
+ title="Sync Stats",
+ colour=discord.Colour.blue(),
+ )
+ for team in grafana_teams:
+ logger.debug(f"Processing {team["name"]}")
+ try:
+ figures = await self._sync_teams(team)
+ except aiohttp.ClientResponseError as e:
+ logger.error(e)
+ if channel:
+ await channel.send(e)
+ continue
+
+ lines = [
+ f"Missing: {figures.added.count}",
+ f"Added: {figures.added.successfully_added}",
+ f"Removed: {figures.removed}",
+ ]
+ embed.add_field(
+ name=team["name"],
+ value="\n".join(lines),
+ inline=False,
+ )
+
+ if channel:
+ await channel.send(embed=embed)
+
+ @sync_github_grafana_teams.error
+ async def on_task_error(self, error: Exception) -> None:
+ """Ensure task errors are output."""
+ logger.error(error)
+
+ @commands.group(name="grafana_github", invoke_without_command=True)
+ async def grafana_group(self, ctx: commands.Context) -> None:
+ """Commands for working with grafana API."""
+ await ctx.send_help(ctx.command)
+
+ @grafana_group.command(name="sync")
+ async def sync_teams(self, ctx: commands.Context) -> None:
+ """Sync Grafana & Github teams now."""
+ await self.sync_github_grafana_teams(ctx.channel)
+
+
+async def setup(bot: KingArthur) -> None:
+ """Add cog to bot."""
+ await bot.add_cog(GrafanaGitHubTeamSync(bot))