/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package se.kth.karamel.backend.github; import org.apache.commons.io.FileUtils; import org.eclipse.egit.github.core.Repository; import org.eclipse.egit.github.core.SearchRepository; import org.eclipse.egit.github.core.User; import org.eclipse.egit.github.core.client.GitHubClient; import org.eclipse.egit.github.core.service.OrganizationService; import org.eclipse.egit.github.core.service.RepositoryService; import org.eclipse.egit.github.core.service.UserService; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.kohsuke.github.GHOrganization; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import se.kth.karamel.common.CookbookScaffolder; import se.kth.karamel.common.exception.KaramelException; import se.kth.karamel.common.util.Confs; import se.kth.karamel.common.util.Settings; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 1. Call registerCredentials() to store your github credentials in memory. 2. Then call methods like addFile(), * commitPush(repo,..) * */ public class GithubApi { private static volatile String user = ""; private static volatile String email = ""; private static volatile String password = ""; private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(GithubApi.class); private static final GitHubClient client = GitHubClient.createClient("http://github.com"); private static final Map<String, List<OrgItem>> cachedOrgs = new HashMap<>(); private static final Map<String, List<RepoItem>> cachedRepos = new HashMap<>(); // Singleton private GithubApi() { } /** * Blindly accepts user credentials, no validation with github. * * @param user * @param password * @return primary github email for the user * @throws se.kth.karamel.common.exception.KaramelException */ public synchronized static GithubUser registerCredentials(String user, String password) throws KaramelException { try { GithubApi.user = user; GithubApi.password = password; client.setCredentials(user, password); client.getUser(); Confs confs = Confs.loadKaramelConfs(); confs.put(Settings.GITHUB_USER_KEY, user); confs.put(Settings.GITHUB_PASSWORD_KEY, password); confs.writeKaramelConfs(); UserService us = new UserService(client); if (us == null) { throw new KaramelException("Could not find user or password incorret: " + user); } User u = us.getUser(); if (u == null) { throw new KaramelException("Could not find user or password incorret: " + user); } GithubApi.email = u.getEmail(); } catch (IOException ex) { logger.warn("Problem connecting to GitHub: " + ex.getMessage()); } return new GithubUser(GithubApi.user, GithubApi.password, GithubApi.email); } /** * * @return email or null if not set yet. */ public static String getEmail() { return GithubApi.email; } public static GithubUser loadGithubCredentials() throws KaramelException { Confs confs = Confs.loadKaramelConfs(); GithubApi.user = confs.getProperty(Settings.GITHUB_USER_KEY); GithubApi.password = confs.getProperty(Settings.GITHUB_PASSWORD_KEY); if (GithubApi.user != null && GithubApi.password != null) { registerCredentials(GithubApi.user, GithubApi.password); } return new GithubUser(GithubApi.user, GithubApi.password, GithubApi.email); } public synchronized static String getUser() { return GithubApi.user; } public synchronized static String getPassword() { return GithubApi.password; } public synchronized static int getRemainingRequests() { return client.getRemainingRequests(); } public synchronized static int getRequestLimit() { return client.getRequestLimit(); } /** * * @return List of github orgs for authenticated user * @throws KaramelException */ public synchronized static List<OrgItem> getOrganizations() throws KaramelException { if (cachedOrgs.get(GithubApi.getUser()) != null) { return cachedOrgs.get(GithubApi.getUser()); } try { List<String> orgs = new ArrayList<>(); OrganizationService os = new OrganizationService(client); List<User> longOrgsList = os.getOrganizations(); List<OrgItem> orgsList = new ArrayList<>(); for (User u : longOrgsList) { orgsList.add(new OrgItem(u.getLogin(), u.getAvatarUrl())); } cachedOrgs.put(GithubApi.getUser(), orgsList); return orgsList; } catch (IOException ex) { throw new KaramelException("Problem listing GitHub organizations: " + ex.getMessage()); } } /** * Gets all repositories for a given organization/user. * * @param orgName * @return List of repositories * @throws KaramelException */ public synchronized static List<RepoItem> getRepos(String orgName) throws KaramelException { if (cachedRepos.get(orgName) != null) { return cachedRepos.get(orgName); } try { RepositoryService rs = new RepositoryService(client); List<Repository> repos; // If we are looking for the repositories for the current user if (GithubApi.getUser().equalsIgnoreCase(orgName)) { repos = rs.getRepositories(orgName); } else { // If we are looking for the repositories for a given organization repos = rs.getOrgRepositories(orgName); } List<RepoItem> repoItems = new ArrayList<>(); for (Repository r : repos) { repoItems.add(new RepoItem(r.getName(), r.getDescription(), r.getSshUrl())); } cachedRepos.put(orgName, repoItems); return repoItems; } catch (IOException ex) { throw new KaramelException("Problem listing GitHub repositories: " + ex.getMessage()); } } public synchronized static boolean repoExists(String owner, String repoName) throws KaramelException { List<RepoItem> repos = GithubApi.getRepos(owner); if (repos == null) { return false; } boolean found = false; for (RepoItem r : repos) { if (r.getName().compareToIgnoreCase(repoName) == 0) { found = true; break; } } return found; } /** * Gets local directory for a given repository name. * * @param repoName * @return File representing the local directory */ public static File getRepoDirectory(String repoName) { File targetDir = new File(Settings.COOKBOOKS_PATH); if (targetDir.exists() == false) { targetDir.mkdirs(); } return new File(Settings.COOKBOOKS_PATH + File.separator + repoName); } /** * Create a repository for a given organization with a description * * @param org * @param repoName * @param description * @return RepoItem bean/json object * @throws KaramelException */ public synchronized static RepoItem createRepoForOrg(String org, String repoName, String description) throws KaramelException { try { OrganizationService os = new OrganizationService(client); RepositoryService rs = new RepositoryService(client); Repository r = new Repository(); r.setName(repoName); r.setOwner(os.getOrganization(org)); r.setDescription(description); rs.createRepository(org, r); cloneRepo(org, repoName); cachedRepos.remove(org); return new RepoItem(repoName, description, r.getSshUrl()); } catch (IOException ex) { throw new KaramelException("Problem creating the repository " + repoName + " for organization " + org + " : " + ex.getMessage()); } } /** * Create a repository in a given github user's local account. * * @param repoName * @param description * @throws KaramelException */ public synchronized static void createRepoForUser(String repoName, String description) throws KaramelException { try { UserService us = new UserService(client); RepositoryService rs = new RepositoryService(client); Repository r = new Repository(); r.setName(repoName); r.setOwner(us.getUser()); r.setDescription(description); rs.createRepository(r); cloneRepo(getUser(), repoName); cachedRepos.remove(GithubApi.getUser()); } catch (IOException ex) { throw new KaramelException("Problem creating " + repoName + " for user " + ex.getMessage()); } } /** * Clone an existing github repo. * * @param owner * @param repoName * @throws se.kth.karamel.common.exception.KaramelException */ public synchronized static void cloneRepo(String owner, String repoName) throws KaramelException { Git result = null; try { RepositoryService rs = new RepositoryService(client); Repository r = rs.getRepository(owner, repoName); String cloneURL = r.getSshUrl(); // prepare a new folder for the cloned repository File localPath = new File(Settings.COOKBOOKS_PATH + File.separator + repoName); if (localPath.isDirectory() == false) { localPath.mkdirs(); } else { throw new KaramelException("Local directory already exists. Delete it first: " + localPath); } logger.debug("Cloning from " + cloneURL + " to " + localPath); result = Git.cloneRepository() .setURI(cloneURL) .setDirectory(localPath) .call(); // Note: the call() returns an opened repository already which needs to be closed to avoid file handle leaks! logger.debug("Cloned repository: " + result.getRepository().getDirectory()); } catch (IOException | GitAPIException ex) { throw new KaramelException("Problem cloning repo: " + ex.getMessage()); } finally { if (result != null) { result.close(); } } } public synchronized static void removeRepo(String owner, String repoName) throws KaramelException { try { GitHub gitHub = GitHub.connectUsingPassword(GithubApi.getUser(), GithubApi.getPassword()); if (!gitHub.isCredentialValid()) { throw new KaramelException("Invalid GitHub credentials"); } GHRepository repo = null; if (owner.compareToIgnoreCase(GithubApi.getUser()) != 0) { GHOrganization org = gitHub.getOrganization(owner); repo = org.getRepository(repoName); } else { repo = gitHub.getRepository(owner + "/" + repoName); } repo.delete(); } catch (IOException ex) { throw new KaramelException("Problem authenticating with gihub-api when trying to remove a repository"); } } public synchronized static void removeLocalRepo(String owner, String repoName) throws KaramelException { File path = getRepoDirectory(repoName); try { FileUtils.deleteDirectory(path); } catch (IOException ex) { throw new KaramelException("Couldn't find the path to delete for Repo: " + repoName + " with owner: " + owner); } } /** * Adds a file to the Github repo's index. If the file already exists, it will delete it and replace its contents with * the new contents. You wil subsequenty need to commit the change and push the commit to github. * * @param owner * @param repoName * @param fileName * @param contents * @throws KaramelException */ public synchronized static void addFile(String owner, String repoName, String fileName, String contents) throws KaramelException { File repoDir = getRepoDirectory(repoName); Git git = null; try { git = Git.open(repoDir); new File(repoDir + File.separator + fileName).delete(); new File(repoDir + File.separator + fileName).getParentFile().mkdirs(); try (PrintWriter out = new PrintWriter(repoDir + File.separator + fileName)) { out.println(contents); } git.add().addFilepattern(fileName).call(); } catch (IOException | GitAPIException ex) { throw new KaramelException(ex.getMessage()); } finally { if (git != null) { git.close(); } } } public synchronized static void removeFile(String owner, String repoName, String fileName) throws KaramelException { File repoDir = getRepoDirectory(repoName); Git git = null; try { git = Git.open(repoDir); new File(repoDir + File.separator + fileName).delete(); git.add().addFilepattern(fileName).call(); git.commit().setAuthor(user, email).setMessage("File removed by Karamel.") .setAll(true).call(); git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(user, password)).call(); RepoItem toRemove = null; List<RepoItem> repos = cachedRepos.get(owner); for (RepoItem r : repos) { if (r.getName().compareToIgnoreCase(repoName) == 0) { toRemove = r; } } if (toRemove != null) { repos.remove(toRemove); } } catch (IOException | GitAPIException ex) { throw new KaramelException(ex.getMessage()); } finally { if (git != null) { git.close(); } } } /** * Scaffolds a Karamel/chef project for an experiment and adds it to the github repo. You still need to commit and * push the changes to github. * * @param repoName * @throws KaramelException */ public static void scaffoldRepo(String repoName) throws KaramelException { File repoDir = getRepoDirectory(repoName); Git git = null; try { git = Git.open(repoDir); CookbookScaffolder.create(repoName); git.add().addFilepattern("Berksfile").addFilepattern("metadata.rb") .addFilepattern("Karamelfile") .addFilepattern(".kitchen.yml").addFilepattern("attributes").addFilepattern("recipes") .addFilepattern("templates").addFilepattern("README.md").call(); } catch (IOException | GitAPIException ex) { throw new KaramelException("Problem scaffolding a new Repository: " + ex.getMessage()); } finally { if (git != null) { git.close(); } } } /** * Synchronizes your updates on your local repository with github. * * @param owner * @param repoName * @throws KaramelException */ public synchronized static void commitPush(String owner, String repoName) throws KaramelException { if (email == null || user == null) { throw new KaramelException("You forgot to call registerCredentials. You must call this method first."); } File repoDir = getRepoDirectory(repoName); Git git = null; try { git = Git.open(repoDir); git.commit().setAuthor(user, email).setMessage("Code generated by Karamel.") .setAll(true).call(); git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(user, password)).call(); } catch (IOException | GitAPIException ex) { logger.error("error during github push", ex); throw new KaramelException(ex.getMessage()); } finally { if (git != null) { git.close(); } } } /** * Search github for organizations/repositories/users using the GitHub API. * * @param query * @throws IOException */ public synchronized static void searchRepos(String query) throws IOException { RepositoryService rs = new RepositoryService(client); List<SearchRepository> listRepos = rs.searchRepositories(query); } }