364 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			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;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| }
 |