package com.hubspot.blazar.visitor.repositorybuild;
import static com.hubspot.blazar.base.RepositoryBuild.State.FAILED;
import static com.hubspot.blazar.base.RepositoryBuild.State.UNSTABLE;
import java.util.Collections;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.hubspot.blazar.base.BuildTrigger;
import com.hubspot.blazar.base.GitInfo;
import com.hubspot.blazar.base.RepositoryBuild;
import com.hubspot.blazar.base.visitor.RepositoryBuildVisitor;
import com.hubspot.blazar.config.BlazarConfiguration;
import com.hubspot.blazar.config.BlazarSlackDirectMessageConfiguration;
import com.hubspot.blazar.data.service.BranchService;
import com.hubspot.blazar.util.BlazarSlackClient;
import com.hubspot.blazar.util.SlackMessageBuildingUtils;
/**
* Direct messages slack users about the state of builds caused by their pushes
*/
public class SlackDmNotificationVisitor implements RepositoryBuildVisitor {
// Cancelled requires a user action - likely the user who would be slacked anyway so it is not worth messaging about
private static final Set<RepositoryBuild.State> SLACK_WORTHY_FAILING_STATES = ImmutableSet.of(FAILED, UNSTABLE);
private static final Logger LOG = LoggerFactory.getLogger(SlackDmNotificationVisitor.class);
private final BlazarSlackDirectMessageConfiguration slackDirectMessageConfig;
private final BranchService branchService;
private final SlackMessageBuildingUtils slackMessageBuildingUtils;
private final BlazarSlackClient blazarSlackClient;
@Inject
public SlackDmNotificationVisitor(BlazarConfiguration blazarConfiguration,
BranchService branchService,
SlackMessageBuildingUtils slackMessageBuildingUtils,
BlazarSlackClient blazarSlackClient) {
this.branchService = branchService;
this.slackMessageBuildingUtils = slackMessageBuildingUtils;
this.blazarSlackClient = blazarSlackClient;
this.slackDirectMessageConfig = blazarConfiguration.getSlackConfiguration().get().getDirectMessageConfiguration();
}
@Override
public void visit(RepositoryBuild build) throws Exception {
if (!build.getState().isComplete()) {
return;
}
Set<String> userEmailsToSendMessagesTo = getUserEmailsToDirectlyNotify(build);
boolean shouldSendMessage = shouldSendMessage(build, userEmailsToSendMessagesTo);
if (shouldSendMessage) {
for (String email : userEmailsToSendMessagesTo) {
String message = "A build started by your code push was not successful";
blazarSlackClient.sendMessageToUser(email, message, slackMessageBuildingUtils.buildSlackAttachment(build));
}
}
}
private Set<String> getUserEmailsToDirectlyNotify(RepositoryBuild build) {
if (!build.getCommitInfo().isPresent()) {
LOG.info("No commit info present cannot determine user to slack");
return Collections.emptySet();
}
Set<String> directNotifyEmails = Sets.newHashSet(
build.getCommitInfo().get().getCurrent().getAuthor().getEmail(), // Author Email
build.getCommitInfo().get().getCurrent().getAuthor().getEmail()); // Committer Email
directNotifyEmails.removeAll(slackDirectMessageConfig.getBlacklistedUserEmails());
// If the white list is empty we send to author/committer
// else we only send to the whitelisted members
if (slackDirectMessageConfig.getWhitelistedUserEmails().isEmpty()) {
return directNotifyEmails;
} else {
return Sets.intersection(directNotifyEmails, slackDirectMessageConfig.getWhitelistedUserEmails());
}
}
private boolean shouldSendMessage(RepositoryBuild build, Set<String> userEmailsToSendMessagesTo) {
if (userEmailsToSendMessagesTo.isEmpty()) {
return false;
}
// Do not send notifications for builds of branches that are explicitly ignored
GitInfo branch = branchService.get(build.getBranchId()).get();
if (slackDirectMessageConfig.getIgnoredBranches().contains(branch.getBranch())) {
LOG.info("Not sending messages for build {} because the branch is in the list of ignored branches {}", build, slackDirectMessageConfig.getIgnoredBranches());
return false;
}
if (!branch.isActive()) {
LOG.info("Branch {} is no longer active, probably was deleted before build finished. Not sending notification for build {}", branch, build);
return false;
}
boolean wasPush = build.getBuildTrigger().getType() == BuildTrigger.Type.PUSH;
boolean wasBranchCreation = build.getBuildTrigger().getType() == BuildTrigger.Type.BRANCH_CREATION;
if (!(wasPush || wasBranchCreation)) {
LOG.info("Not sending messages for triggers other than push or branch creation at this time.");
return false;
}
if (!SLACK_WORTHY_FAILING_STATES.contains(build.getState())) {
LOG.info("Build {} in state {} does not merit direct-slack-message", build.getId().get(), build.getState());
return false;
}
return true;
}
}