package de.refactoringbot.api.gitlab; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import com.fasterxml.jackson.databind.ObjectMapper; import de.refactoringbot.configuration.BotConfiguration; import de.refactoringbot.model.configuration.GitConfiguration; import de.refactoringbot.model.exceptions.BotRefactoringException; import de.refactoringbot.model.exceptions.GitLabAPIException; import de.refactoringbot.model.exceptions.ValidationException; import de.refactoringbot.model.gitlab.pullrequest.GitLabCreateRequest; import de.refactoringbot.model.gitlab.pullrequest.GitLabPullRequest; import de.refactoringbot.model.gitlab.pullrequest.GitLabPullRequests; import de.refactoringbot.model.gitlab.pullrequestdiscussion.GitLabDiscussion; import de.refactoringbot.model.gitlab.pullrequestdiscussion.GitLabDiscussions; import de.refactoringbot.model.gitlab.repository.GitLabRepository; import de.refactoringbot.model.gitlab.user.GitLabUser; /** * This class communicates with the Gitlab-API. * * @author Stefan Basaric * */ @Component public class GitlabDataGrabber { @Autowired ObjectMapper mapper; @Autowired BotConfiguration botConfig; private static final Logger logger = LoggerFactory.getLogger(GitlabDataGrabber.class); private static final String USER_AGENT = "Mozilla/5.0"; private static final String GITLAB_DEFAULT_APILINK = "https://gitlab.com/api/v4"; private static final String TOKEN_HEADER = "Private-Token"; /** * This method tries to get a repository from GitLab. * * @param repoName * @param repoOwner * @param botToken * @param apiLink * @throws GitLabAPIException */ public GitLabRepository checkRepository(String repoName, String repoOwner, String botToken, String apiLink) throws GitLabAPIException { // Build URI URI gitlabURI = null; if (apiLink == null || apiLink.isEmpty()) { gitlabURI = URI.create(GITLAB_DEFAULT_APILINK + "/projects/" + repoOwner + "%2F" + repoName); } else { gitlabURI = URI.create(apiLink + "/projects/" + repoOwner + "%2F" + repoName); } RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, botToken); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send request to the GitLab-API return rest.exchange(gitlabURI, HttpMethod.GET, entity, GitLabRepository.class).getBody(); } catch (RestClientException e) { logger.error(e.getMessage(), e); throw new GitLabAPIException("Repository does not exist on GitLab or invalid Bot-Token!", e); } } /** * This method tries to get a user with the given token. * * @param botUsername * @param botToken * @param botEmail * @param apiLink * @return * @throws Exception */ public void checkGitlabUser(String botUsername, String botToken, String botEmail, String apiLink) throws Exception { // Build URI URI gitlabURI = null; if (apiLink == null || apiLink.isEmpty()) { gitlabURI = URI.create(GITLAB_DEFAULT_APILINK + "/user"); } else { gitlabURI = URI.create(apiLink + "/user"); } RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, botToken); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); GitLabUser gitLabUser = null; try { // Send request to the GitLab-API gitLabUser = rest.exchange(gitlabURI, HttpMethod.GET, entity, GitLabUser.class).getBody(); } catch (RestClientException e) { logger.error(e.getMessage(), e); throw new GitLabAPIException("Invalid Bot-Token!"); } // Check if user exists and has a public email if (!botUsername.equals(gitLabUser.getUsername())) { throw new ValidationException("Bot-User does not exist on GitLab!"); } if (gitLabUser.getPublicEmail() == null || gitLabUser.getPublicEmail().isEmpty()) { throw new ValidationException("Bot-User does not have a public email on GitLab!"); } if (!gitLabUser.getPublicEmail().equals(botEmail)) { throw new ValidationException("Invalid Bot-Email!"); } } /** * This method checks if a branch with a specific name exists on the fork. If * such a branch exists, the method throws an exception. * * @param gitConfig * @param branchName * @throws URISyntaxException * @throws BotRefactoringException * @throws GitLabAPIException */ public void checkBranch(GitConfiguration gitConfig, String branchName) throws URISyntaxException, BotRefactoringException, GitLabAPIException { // Build URI URI uri = createURIFromApiLink(gitConfig.getForkApiLink() + "/repository/branches/" + branchName); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send Request to the GitLab-API rest.exchange(uri, HttpMethod.GET, entity, String.class).getBody(); throw new BotRefactoringException( "Issue was already refactored in the past! The bot database might have been resetted but not the fork itself."); } catch (RestClientException e) { // If branch does not exist -> return if (e.getMessage().equals("404 Not Found")) { return; } logger.error(e.getMessage(), e); throw new GitLabAPIException("Could not get Branch from GitLab!", e); } } /** * This method creates a fork from a repository. * * @param gitConfig * @return * @throws URISyntaxException * @throws GitLabAPIException */ public GitLabRepository createFork(GitConfiguration gitConfig) throws URISyntaxException, GitLabAPIException { // Build URI URI forkUri = createURIFromApiLink(gitConfig.getRepoApiLink() + "/fork"); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send request to the GitLab-API return rest.exchange(forkUri, HttpMethod.POST, entity, GitLabRepository.class).getBody(); } catch (RestClientException r) { throw new GitLabAPIException("Could not create fork on GitLab!", r); } } /** * This method deletes a repository from GitLab. * * @param gitConfig * @throws URISyntaxException * @throws GitLabAPIException */ public void deleteRepository(GitConfiguration gitConfig) throws URISyntaxException, GitLabAPIException { String originalRepo = gitConfig.getRepoApiLink(); String forkRepo = gitConfig.getForkApiLink(); // Never delete the original repository if (originalRepo.equals(forkRepo)) { return; } // Read URI from configuration URI repoUri = createURIFromApiLink(gitConfig.getForkApiLink()); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send request to the GitLab-API rest.exchange(repoUri, HttpMethod.DELETE, entity, String.class); } catch (RestClientException r) { throw new GitLabAPIException("Could not delete repository from GitLab!", r); } } /** * This method returns all PullRequest from GitLab. * * @return allRequests * @throws URISyntaxException * @throws GitLabAPIException * @throws IOException */ public GitLabPullRequests getAllPullRequests(GitConfiguration gitConfig) throws URISyntaxException, GitLabAPIException, IOException { // Build URI URI requestUri = createURIFromApiLink(gitConfig.getRepoApiLink() + "/merge_requests?state=opened"); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); String json = null; try { // Send Request to the GitLab-API json = rest.exchange(requestUri, HttpMethod.GET, entity, String.class).getBody(); } catch (RestClientException e) { logger.error(e.getMessage(), e); throw new GitLabAPIException("Could not get Pull-Requests from GitLab!", e); } GitLabPullRequests allRequests = new GitLabPullRequests(); try { List<GitLabPullRequest> requestList = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, GitLabPullRequest.class)); allRequests.setAllPullRequests(requestList); return allRequests; } catch (IOException e) { logger.error(e.getMessage(), e); throw new IOException("Could not create object from GitLab-Request json!", e); } } /** * This method returns all comments of a specific pull request from GitLab. * * @param commentUri * @param gitConfig * @return allComments * @throws GitLabAPIException * @throws IOException */ public GitLabDiscussions getAllPullRequestDiscussions(URI commentUri, GitConfiguration gitConfig) throws GitLabAPIException, IOException { RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); String json = null; try { // Send request to the GitLab-API json = rest.exchange(commentUri, HttpMethod.GET, entity, String.class).getBody(); } catch (RestClientException r) { throw new GitLabAPIException("Could not get pull request comments from GitLab!", r); } try { // map json to object GitLabDiscussions allDiscussions = new GitLabDiscussions(); List<GitLabDiscussion> discussionList = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, GitLabDiscussion.class)); allDiscussions.setDiscussions(discussionList); return allDiscussions; } catch (IOException e) { logger.error(e.getMessage(), e); throw new IOException("Could not create object from GitLab-Comment json!", e); } } /** * This method creates a pull request on GitLab. * * @param createRequest * @param gitConfig * @return newPullRequest * @throws URISyntaxException * @throws GitLabAPIException */ public GitLabPullRequest createRequest(GitLabCreateRequest createRequest, GitConfiguration gitConfig) throws URISyntaxException, GitLabAPIException { // Build URI URI uri = createURIFromApiLink(gitConfig.getForkApiLink() + "/merge_requests"); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(uri.getScheme()) .host(uri.getHost()).path(uri.getPath()); apiUriBuilder.queryParam("title", createRequest.getTitle()); apiUriBuilder.queryParam("description", createRequest.getDescription()); apiUriBuilder.queryParam("source_branch", createRequest.getSource_branch()); apiUriBuilder.queryParam("target_branch", createRequest.getTarget_branch()); apiUriBuilder.queryParam("allow_collaboration", createRequest.isAllow_collaboration()); apiUriBuilder.queryParam("target_project_id", createRequest.getTarget_project_id()); uri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send request to the GitLab-API return rest.exchange(uri, HttpMethod.POST, entity, GitLabPullRequest.class).getBody(); } catch (RestClientException r) { throw new GitLabAPIException("Could not create pull request on GitLab!", r); } } /** * This method responds to the user inside the GitLabDiscussion. * * @param gitConfig * @param pullRequestIid * @param discussionId * @param noteId * @param message * @throws GitLabAPIException * @throws URISyntaxException */ public void respondToUser(GitConfiguration gitConfig, Integer pullRequestIid, String discussionId, Integer noteId, String message) throws GitLabAPIException, URISyntaxException { // Build URI URI uri = createURIFromApiLink(gitConfig.getRepoApiLink() + "/merge_requests/" + pullRequestIid + "/discussions/" + discussionId + "/notes"); // Build URI UriComponentsBuilder apiUriBuilder = UriComponentsBuilder.newInstance().scheme(uri.getScheme()) .host(uri.getHost()).path(uri.getPath()); apiUriBuilder.queryParam("note_id", noteId); apiUriBuilder.queryParam("body", message); uri = apiUriBuilder.build().encode().toUri(); RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("User-Agent", USER_AGENT); headers.set(TOKEN_HEADER, gitConfig.getBotToken()); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); try { // Send request to the GitLab-API rest.exchange(uri, HttpMethod.POST, entity, String.class).getBody(); } catch (RestClientException r) { throw new GitLabAPIException("Could not reply to user on GitLab!", r); } } /** * Attempts to instantiate a URI object using the specified API link * * @param link * @return uri * @throws URISyntaxException */ private URI createURIFromApiLink(String link) throws URISyntaxException { URI result = null; try { result = new URI(link); } catch (URISyntaxException u) { logger.error(u.getMessage(), u); throw new URISyntaxException("Could not create URI from given API link!", u.getMessage()); } return result; } }