Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manage GitHub teams permissions as code #3998

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f100edb
add team sync function
Alaurant Jul 7, 2024
d2e1f1a
fix upper case issue
Alaurant Jul 7, 2024
4d25a7b
Rename yamlTeamLoader.java to YAMLTeamLoader.java
Alaurant Jul 7, 2024
59b6820
fix safe path
Alaurant Jul 7, 2024
632e24e
change file type
Alaurant Jul 7, 2024
1a7ce9f
change workflow path
Alaurant Jul 7, 2024
ca4e835
Update naming conventions
Alaurant Jul 22, 2024
a7b86fd
Update variable names
Alaurant Jul 22, 2024
3674be9
Update related variable names
Alaurant Jul 22, 2024
3654aff
Update funcs based on feedback
Alaurant Jul 29, 2024
5da05b9
Merge branch 'jenkins-infra:master' into master
Alaurant Jul 29, 2024
1c15445
update definition file
Alaurant Jul 29, 2024
7a853aa
update definition name
Alaurant Jul 29, 2024
ecad765
remove system exit
Alaurant Jul 29, 2024
93d4959
Fix case sensitivity issues in filenames
Alaurant Jul 29, 2024
bb6369c
Remove unwanted file with incorrect case
Alaurant Jul 29, 2024
d02abbb
new features
Alaurant Aug 24, 2024
9305041
Merge branch 'jenkins-infra:master' into master
Alaurant Aug 24, 2024
2138e2d
remove first run check
Alaurant Aug 26, 2024
6a11556
Merge branch 'jenkins-infra:master' into master
Alaurant Aug 26, 2024
45a2b0f
Merge branch 'feature'
Alaurant Aug 26, 2024
5e3d6af
fix the problem of static method
Alaurant Aug 26, 2024
c3f766d
Revert "fix the problem of static method"
Alaurant Aug 26, 2024
c8993bd
Provide a default or empty file path
Alaurant Aug 26, 2024
e24ecda
update static method
Alaurant Aug 26, 2024
3f0fc8f
Merge branch 'jenkins-infra:master' into master
Alaurant Sep 9, 2024
9d7a0aa
Merge branch 'jenkins-infra:master' into master
Alaurant Sep 12, 2024
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
40 changes: 40 additions & 0 deletions .github/workflows/teamsync_check_for_changes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: teamSync check for changes

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
check-permissions:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'adopt'

- name: Build project
run: mvn clean install

- name: Check for yml changes
id: files
run: |
FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} -- 'permissions/*.yml' | xargs echo)
if [[ -z "$FILES" ]]; then
echo "No changes detected in permissions files."
else
echo "Changes detected. Processing..."
java -jar target/github_team_sync.jar $FILES
fi
env:
GITHUB_OAUTH: ${{ secrets.TEMP_TEAMSYNC_PAT }}
30 changes: 29 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,32 @@
<phase>package</phase>
<configuration>
<descriptors>
<descriptor>src/assembly.xml</descriptor>
<descriptor>src/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
<execution> <!-- GitHub TeamSync -->
<id>GitHub TeamSync</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/assembly/teamSync.xml</descriptor>
</descriptors>
<archive>
<manifest>
<mainClass>io.jenkins.infra.repository_permissions_updater.github_team_sync.TeamSyncExecutor</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>github_team_sync</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Expand Down Expand Up @@ -234,6 +256,12 @@
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
12 changes: 0 additions & 12 deletions src/assembly.xml

This file was deleted.

22 changes: 22 additions & 0 deletions src/assembly/assembly.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<assembly>
<id>bin</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>target/classes/</directory>
<outputDirectory>/</outputDirectory>
<excludes>
<exclude>io/jenkins/infra/repository_permissions_updater/github_team_sync/*.class
</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
17 changes: 17 additions & 0 deletions src/assembly/teamSync.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<assembly>
<id>GitHub TeamSync</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>target/classes/</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>io/jenkins/infra/repository_permissions_updater/github_team_sync/*.class
</include>
</includes>
</fileSet>
</fileSets>
</assembly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.jenkins.infra.repository_permissions_updater.github_team_sync;

public class AdditionalTeamDefinition {
private String teamName;
private Role role;

public AdditionalTeamDefinition(String teamName, String role) {
this.teamName = teamName;
this.role = validateRole(role);
}

public String getName() {
return teamName;
}

public Role getRole() {
return role;
}

private Role validateRole(String role) {
if (role == null) {
return null;
}
try {
return Role.valueOf(role.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid team role: " + role);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.jenkins.infra.repository_permissions_updater.github_team_sync;

import java.io.IOException;
import java.util.Set;

import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTeam;

public interface GitHubService {

GHOrganization getOrganization(String name) throws IOException;

void addDeveloperToTeam(GHTeam team, String developer) throws IOException;

void removeDeveloperFromTeam(GHTeam team, String developer) throws IOException;

Set<String> getCurrentTeamMembers(GHTeam team) throws IOException;

GHTeam createTeam(String orgName, String teamName, GHTeam.Privacy privacy) throws IOException;

void updateTeamRole(GHRepository repo, GHTeam ghTeam, Role role) throws IOException;

GHTeam getTeamFromRepo(String orgName, String repoName, String teamName) throws IOException;

void removeTeamFromRepository(GHTeam team, GHRepository repo) throws IOException;

Set<String> getCurrentTeams(GHRepository repo, GHTeam repoTeam) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.jenkins.infra.repository_permissions_updater.github_team_sync;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.kohsuke.github.*;


public class GitHubServiceImpl implements GitHubService {
private GitHub github;

private static final Map<Role, GHOrganization.Permission> PERMISSIONS_MAP = Map.of(
Role.READ, GHOrganization.Permission.PULL,
Role.TRIAGE, GHOrganization.Permission.TRIAGE,
Role.WRITE, GHOrganization.Permission.PUSH,
Role.MAINTAIN, GHOrganization.Permission.MAINTAIN,
Role.ADMIN, GHOrganization.Permission.ADMIN
);

public GitHubServiceImpl(String oauthToken) {
try {
this.github = new GitHubBuilder().withOAuthToken(oauthToken).build();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public GHTeam getTeamFromRepo(
String repoName, String orgName, String teamName) throws IOException {
GHOrganization org = github.getOrganization(orgName);
GHRepository repo = org.getRepository(repoName);
Set<GHTeam> teams = ((GHRepository) repo).getTeams();

for (GHTeam team : teams) {
if (team.getName().equals(teamName)) {
return team;
}
}
return null;
}

@Override
public GHOrganization getOrganization(String name) throws IOException {
return github.getOrganization(name);
}


@Override
public void addDeveloperToTeam(GHTeam team, String developer) throws IOException {
GHUser user = github.getUser(developer);
team.add(user);
}

@Override
public void removeDeveloperFromTeam(GHTeam team, String developer) throws IOException {
GHUser user = github.getUser(developer);
team.remove(user);
}

@Override
public Set<String> getCurrentTeamMembers(GHTeam team) throws IOException {
Set<String> members = new HashSet<>();
for (GHUser member : team.listMembers()) {
members.add(member.getLogin());
}
return members;
}

@Override
public GHTeam createTeam(String orgName, String teamName, GHTeam.Privacy privacy) throws IOException {
GHOrganization org = github.getOrganization(orgName);
return org.createTeam(teamName).privacy(privacy).create();
}

@Override
public void updateTeamRole(GHRepository repo, GHTeam ghTeam, Role role) throws IOException {
GHOrganization.Permission permission = PERMISSIONS_MAP.get(role);
GHOrganization.RepositoryRole repoRole = GHOrganization.RepositoryRole.from(permission);
ghTeam.add(repo, repoRole);
}

@Override
public void removeTeamFromRepository(GHTeam team, GHRepository repo) throws IOException {
team.remove(repo);
}

/**
* Retrieves the names of all additional teams associated with the given GitHub repository, excluding the repo team.
* This method returns only team names because the current Java GitHub API does not support retrieving roles
* that teams hold within specific repositories. Therefore, role-related information is not available.
*/
@Override
public Set<String> getCurrentTeams(GHRepository repo, GHTeam repoTeam) throws IOException {
Set<GHTeam> allTeams = repo.getTeams();

return allTeams.stream()
.filter(team -> !team.equals(repoTeam))
.map(GHTeam::getName)
.collect(Collectors.toSet());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.jenkins.infra.repository_permissions_updater.github_team_sync;

import java.util.Set;

public class RepoTeamDefinition {

private String repoName;
private String orgName;
private String teamName;

private static final String DEFAULT_ORG_NAME = "jenkinsci";
private final Role role = Role.ADMIN;
private Set<String> developers;
private Set<AdditionalTeamDefinition> additionalTeams;

public RepoTeamDefinition(String repoName, String orgName, String teamName,
Set<String> developers, Set<AdditionalTeamDefinition> additionalTeams) {
this.repoName = repoName;
this.orgName = orgName != null ? orgName : DEFAULT_ORG_NAME;
this.teamName = teamName;
this.developers = developers;
this.additionalTeams = additionalTeams;
}

public RepoTeamDefinition() {
}


public String getRepoName() {
return repoName;
}

public String getOrgName() {
return orgName;
}

public String getTeamName() {
return teamName;
}

public Role getRole() {
return role;
}

public Set<String> getDevelopers() {
return developers;
}

public Set<AdditionalTeamDefinition> getAdditionalTeams() {
return additionalTeams;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.jenkins.infra.repository_permissions_updater.github_team_sync;

public enum Role {
READ,
TRIAGE,
WRITE,
MAINTAIN,
ADMIN
}
Loading