GithubInitializer/src/main/java/nl/andrewlalis/git_api/GithubManager.java

364 lines
15 KiB
Java

package nl.andrewlalis.git_api;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import nl.andrewlalis.model.Student;
import nl.andrewlalis.model.StudentTeam;
import nl.andrewlalis.model.TATeam;
import nl.andrewlalis.model.TeachingAssistant;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.kohsuke.github.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Logger;
/**
* This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc.
*/
public class GithubManager {
/**
* Github object for API interactions.
*/
private GitHub github;
private GHOrganization organization;
private String accessToken;
/**
* The logger for outputting debug info.
*/
private static final Logger logger = Logger.getLogger(GithubManager.class.getName());
static {
logger.setParent(Logger.getGlobal());
}
public GithubManager(String organizationName, String accessToken) {
this.accessToken = accessToken;
try {
this.github = GitHub.connectUsingOAuth(accessToken);
this.organization = this.github.getOrganization(organizationName);
} catch (IOException e) {
logger.severe("Unable to make a GithubManager with organization name: " + organizationName + " and access token: " + accessToken);
e.printStackTrace();
}
}
/**
* Returns a list of repositories with the given substring.
* @param substring A string which all repositories should contain.
* @return A List of repositories whose names contain the given substring.
*/
public List<GHRepository> listReposWithPrefix(String substring) {
List<GHRepository> repos = new ArrayList<>();
try {
List<GHRepository> allRepos = this.organization.listRepositories().asList();
for (GHRepository repo : allRepos) {
if (repo.getName().contains(substring)) {
repos.add(repo);
}
}
} catch (Exception e) {
logger.severe("IOException while listing repositories in the organization.");
e.printStackTrace();
}
return repos;
}
/**
* Gets a repository by name.
* @param name The name of the repository.
* @return The repository with the given name, or null if none exists.
*/
public GHRepository getRepository(String name) {
System.out.println(name);
try {
return this.organization.getRepository(name);
} catch (IOException e) {
logger.severe("No repository with name: " + name + " exists.");
e.printStackTrace();
return null;
}
}
/**
* Gets a list of teams in the organization.
* @return A List of all TA teams in the organization.
*/
public List<TATeam> getTeams() {
List<TATeam> teams = new ArrayList<>();
try {
Random rand = new Random();
for (Map.Entry<String, GHTeam> entry : this.organization.getTeams().entrySet()) {
TATeam team = new TATeam(entry.getKey(), entry.getValue().getId());
team.setGithubTeam(entry.getValue());
for (GHUser user : entry.getValue().listMembers().asList()) {
team.addMember(new TeachingAssistant(rand.nextInt(), user.getName(), user.getEmail(), user.getLogin()));
}
teams.add(team);
}
} catch (IOException e) {
logger.severe("Could not get a list of teams in the organization.\n" + e.getMessage());
e.printStackTrace();
}
return teams;
}
/**
* Gets a list of all teaching assistants, or members, in the organization.
* @return A List of teaching assistants, and empty if an error occurred.
*/
public List<TeachingAssistant> getMembers() {
List<TeachingAssistant> teachingAssistants = new ArrayList<>();
try {
for (GHUser member : this.organization.listMembers().asList()) {
teachingAssistants.add(new TeachingAssistant(-1, member.getName(), member.getEmail(), member.getLogin()));
}
} catch (IOException e) {
logger.severe("Could not get list of members in the organization.\n" + e.getMessage());
e.printStackTrace();
}
return teachingAssistants;
}
/**
* Sets up the organization's assignments repository, and grants permissions to all teaching assistants.
* @param assignmentsRepoName The name of the assignments repository.
* @param description The description of the repository.
* @param allTeachingAssistants The name of the team consisting of all teaching assistants.
* @throws IOException If an HTTP request failed.
*/
public void setupAssignmentsRepo(String assignmentsRepoName, String description, String allTeachingAssistants) throws IOException {
GHTeam team = this.organization.getTeamByName(allTeachingAssistants);
// Check if the repository already exists.
GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName);
if (existingRepo != null) {
existingRepo.delete();
logger.fine("Deleted pre-existing assignments repository.");
}
GHRepository assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, true);
if (assignmentsRepo == null) {
logger.severe("Could not create assignments repository.");
return;
}
this.protectMasterBranch(assignmentsRepo, team);
// Grant all teaching assistants write access.
team.add(assignmentsRepo, GHOrganization.Permission.ADMIN);
logger.fine("Gave admin rights to all teaching assistants in team: " + team.getName());
}
/**
* Creates and sets up a student team's repository, and invites those students to the organization's assignments
* repository as well.
* @param team The student team to set up.
* @param taTeam The team of teaching assistants that is responsible for these students.
* @param prefix The prefix to append to the front of the repo name.
* @param assignmentsRepo The assignments repository.
*/
public void setupStudentRepo(StudentTeam team, TATeam taTeam, String prefix, GHRepository assignmentsRepo) {
// First check that the assignments repo exists, otherwise no invitations can be sent.
if (assignmentsRepo == null) {
logger.warning("Assignments repository must be created before student repositories.");
return;
}
GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true);
if (repo == null) {
logger.severe("Repository for student team " + team.getNumber() + " could not be created.");
return;
}
team.setRepository(repo);
team.setTaTeam(taTeam);
this.protectMasterBranch(repo, taTeam.getGithubTeam());
this.createDevelopmentBranch(repo);
this.addTATeamAsAdmin(repo, taTeam.getGithubTeam());
this.inviteStudentsToRepos(team, assignmentsRepo);
}
/**
* Deletes all repositories in the organization.
* @param substring The substring which repository names should contain to be deleted.
*/
public void deleteAllRepositories(String substring) {
List<GHRepository> repositories = this.organization.listRepositories().asList();
for (GHRepository repo : repositories) {
if (repo.getName().contains(substring)) {
try {
repo.delete();
logger.info("Deleted repository: " + repo.getName());
} catch (IOException e) {
logger.severe("Could not delete repository: " + repo.getName());
e.printStackTrace();
}
}
}
}
/**
* Archives all repositories whose name contains the given substring.
* @param sub Any repository containing this substring will be archived.
*/
public void archiveAllRepositories(String sub) {
List<GHRepository> repositories = this.organization.listRepositories().asList();
for (GHRepository repo : repositories) {
if (repo.getName().contains(sub)) {
archiveRepository(repo);
}
}
}
/**
* Archives a repository so that it can no longer be manipulated.
* TODO: Change to using Github API instead of Apache HttpUtils.
* @param repo The repository to archive.
*/
private void archiveRepository(GHRepository repo) {
try {
HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken);
CloseableHttpClient client = HttpClientBuilder.create().build();
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
root.put("archived", true);
String json = mapper.writeValueAsString(root);
patch.setEntity(new StringEntity(json));
HttpResponse response = client.execute(patch);
if (response.getStatusLine().getStatusCode() != 200) {
throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode());
}
logger.info("Archived repository: " + repo.getFullName());
} catch (IOException e) {
logger.severe("Could not archive repository: " + repo.getName());
e.printStackTrace();
}
}
/**
* Invites students in a team to their repository, and the assignments repository.
* @param team The team of students to invite as collaborators.
* @param assignmentsRepo The repository that contains assignments for the class.
*/
private void inviteStudentsToRepos(StudentTeam team, GHRepository assignmentsRepo) {
try {
logger.finest("Adding students from team: " + team.getNumber() + " as collaborators.");
for (Student student : team.getStudents()) {
GHUser user = this.github.getUser(student.getGithubUsername());
this.addCollaboratorToRepo(user, assignmentsRepo);
this.addCollaboratorToRepo(user, team.getRepository());
}
} catch (IOException e) {
logger.severe("Could not add students as collaborators to assignments or their repo.\n" + team);
e.printStackTrace();
}
}
/**
* Adds a user to a repository, or if a failure occurs, log the failure.
* @param user The user to add as a collaborator.
* @param repository The repository to add the user to.
*/
private void addCollaboratorToRepo(GHUser user, GHRepository repository) {
try {
repository.addCollaborators(user);
} catch (IOException e) {
logger.severe("Could not add user " + user.getLogin() + " to repository " + repository.getName());
}
}
/**
* Adds a teaching assistant team as admins to a particular student repository.
* @param studentRepo The student repository.
* @param taTeam The team to give admin rights.
*/
private void addTATeamAsAdmin(GHRepository studentRepo, GHTeam taTeam) {
try {
taTeam.add(studentRepo, GHOrganization.Permission.ADMIN);
logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + studentRepo.getName());
} catch (IOException e) {
logger.severe("Could not add TA Team: " + taTeam.getName() + " as admins to repository: " + studentRepo.getName());
e.printStackTrace();
}
}
/**
* Protects the master branch of a given repository, and gives admin rights to the given team.
* @param repo The repository to protect the master branch of.
* @param team The team which gets admin rights to the master branch.
*/
@SuppressWarnings("deprecation")
private void protectMasterBranch(GHRepository repo, GHTeam team) {
try {
GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection();
protectionBuilder.includeAdmins(false);
protectionBuilder.restrictPushAccess();
protectionBuilder.teamPushAccess(team);
protectionBuilder.addRequiredChecks("ci/circleci");
protectionBuilder.enable();
logger.fine("Protected master branch of repository: " + repo.getName());
} catch (IOException e) {
logger.severe("Could not protect master branch of repository: " + repo.getName());
e.printStackTrace();
}
}
/**
* Creates a development branch for the given repository.
* @param repo The repository to create a development branch for.
*/
private void createDevelopmentBranch(GHRepository repo) {
try {
String sha1 = repo.getBranch(repo.getDefaultBranch()).getSHA1();
repo.createRef("refs/heads/development", sha1);
logger.fine("Created development branch of repository: " + repo.getName());
} catch (IOException e) {
logger.severe("Could not create development branch for repository: " + repo.getName());
e.printStackTrace();
}
}
/**
* Creates a new github repository.
* @param name The name of the repository.
* @param taTeam The team to give admin rights.
* @param description The description of the repository.
* @param hasWiki Whether the repo has a wiki enabled.
* @param hasIssues Whether the repo has issues enabled.
* @param isPrivate Whether or not the repository is private.
* @return The repository that was created, or null if it could not be created.
*/
private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){
try {
GHCreateRepositoryBuilder builder = this.organization.createRepository(name);
builder.team(taTeam);
builder.wiki(hasWiki);
builder.issues(hasIssues);
builder.description(description);
builder.gitignoreTemplate("Java");
builder.private_(isPrivate);
GHRepository repo = builder.create();
logger.fine("Created repository: " + repo.getName());
return repo;
} catch (IOException e) {
logger.severe("Could not create repository: " + name);
e.printStackTrace();
return null;
}
}
}