diff --git a/src/main/java/io/quarkus/bot/PullRequestCommandHandler.java b/src/main/java/io/quarkus/bot/PullRequestCommandHandler.java new file mode 100644 index 00000000..0eb58a3f --- /dev/null +++ b/src/main/java/io/quarkus/bot/PullRequestCommandHandler.java @@ -0,0 +1,77 @@ +package io.quarkus.bot; + +import io.quarkiverse.githubapp.event.IssueComment; +import io.quarkus.bot.command.Command; +import io.quarkus.bot.config.QuarkusBotConfig; +import org.jboss.logging.Logger; +import org.kohsuke.github.GHEventPayload; +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHPermissionType; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.ReactionContent; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import java.io.IOException; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PullRequestCommandHandler { + + private static final Logger LOG = Logger.getLogger(PullRequestCommandHandler.class); + + private static final String QUARKUS_BOT_NAME = "quarkus-bot[bot]"; + private static final Pattern QUARKUS_BOT_MENTION = Pattern.compile("^@(?:quarkus-?)?bot\\s+([a-z _\\-]+)"); + + @Inject + Instance> commands; + + @Inject + QuarkusBotConfig quarkusBotConfig; + + public void onComment(@IssueComment.Created @IssueComment.Edited GHEventPayload.IssueComment commentPayload) + throws IOException { + GHUser user = commentPayload.getComment().getUser(); + GHIssue issue = commentPayload.getIssue(); + GHRepository repository = commentPayload.getRepository(); + + if (QUARKUS_BOT_NAME.equals(commentPayload.getComment().getUserName())) { + return; + } + + if (issue.isPullRequest()) { + Optional> command = extractCommand(commentPayload.getComment().getBody()); + if (command.isPresent() && canRunCommand(repository, user)) { + GHPullRequest pullRequest = repository.getPullRequest(issue.getNumber()); + ReactionContent reactionResult = command.get().run(pullRequest); + postReaction(commentPayload, issue, reactionResult); + } else { + postReaction(commentPayload, issue, ReactionContent.MINUS_ONE); + } + } + } + + private void postReaction(GHEventPayload.IssueComment comment, GHIssue issue, ReactionContent reactionResult) throws IOException { + if (!quarkusBotConfig.isDryRun()) { + comment.getComment().createReaction(reactionResult); + } else { + LOG.info("Pull Request #" + issue.getNumber() + " - Add reaction: " + reactionResult.getContent()); + } + } + + private Optional> extractCommand(String comment) { + Matcher matcher = QUARKUS_BOT_MENTION.matcher(comment); + if (matcher.matches()) { + String commandLabel = matcher.group(1); + return commands.stream().filter(command -> command.labels().contains(commandLabel)).findFirst(); + } + return Optional.empty(); + } + + private boolean canRunCommand(GHRepository repository, GHUser user) throws IOException { + return repository.getPermission(user) == GHPermissionType.WRITE || repository.getPermission(user) == GHPermissionType.ADMIN; + } +} diff --git a/src/main/java/io/quarkus/bot/command/Command.java b/src/main/java/io/quarkus/bot/command/Command.java new file mode 100644 index 00000000..de70251d --- /dev/null +++ b/src/main/java/io/quarkus/bot/command/Command.java @@ -0,0 +1,14 @@ +package io.quarkus.bot.command; + +import org.kohsuke.github.ReactionContent; + +import java.io.IOException; +import java.util.List; + +public interface Command { + + List labels(); + + ReactionContent run(T input) throws IOException; + +} diff --git a/src/main/java/io/quarkus/bot/command/RerunWorkflowCommand.java b/src/main/java/io/quarkus/bot/command/RerunWorkflowCommand.java new file mode 100644 index 00000000..2f08874e --- /dev/null +++ b/src/main/java/io/quarkus/bot/command/RerunWorkflowCommand.java @@ -0,0 +1,63 @@ +package io.quarkus.bot.command; + +import io.quarkus.bot.config.QuarkusBotConfig; +import io.quarkus.bot.workflow.WorkflowConstants; +import org.jboss.logging.Logger; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHWorkflowRun; +import org.kohsuke.github.ReactionContent; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@ApplicationScoped +public class RerunWorkflowCommand implements Command { + + private static final Logger LOG = Logger.getLogger(RerunWorkflowCommand.class); + + @Inject + QuarkusBotConfig quarkusBotConfig; + + @Override + public List labels() { + return Arrays.asList("test", "retest"); + } + + @Override + public ReactionContent run(GHPullRequest pullRequest) throws IOException { + GHRepository repository = pullRequest.getRepository(); + + List ghWorkflowRuns = repository + .queryWorkflowRuns() + .branch(pullRequest.getHead().getRef()) + .status(GHWorkflowRun.Status.COMPLETED) + .list().toList(); + + Map> lastWorkflowRuns = ghWorkflowRuns.stream() + .filter(workflowRun -> WorkflowConstants.QUARKUS_CI_WORKFLOW_NAME.equals(workflowRun.getName()) + || WorkflowConstants.QUARKUS_DOCUMENTATION_CI_WORKFLOW_NAME.equals(workflowRun.getName())) + .filter(workflowRun -> workflowRun.getHeadRepository().getOwnerName() + .equals(pullRequest.getHead().getRepository().getOwnerName())) + .collect(Collectors.groupingBy(GHWorkflowRun::getName, Collectors.maxBy(Comparator.comparing(GHWorkflowRun::getRunNumber)))); + + for (Map.Entry> lastWorkflowRun : lastWorkflowRuns.entrySet()) { + if (lastWorkflowRun.getValue().isPresent()) { + if (!quarkusBotConfig.isDryRun()) { + lastWorkflowRun.getValue().get().rerun(); + LOG.info("Pull request #" + pullRequest.getNumber() + " - Restart workflow: " + lastWorkflowRun.getValue().get().getHtmlUrl()); + } else { + LOG.info("Pull request #" + pullRequest.getNumber() + " - Restart workflow " + lastWorkflowRun.getKey()); + } + } + } + return ReactionContent.ROCKET; + } +}