package slacknotifications.teamcity;
import jetbrains.buildServer.responsibility.ResponsibilityEntry;
import jetbrains.buildServer.responsibility.TestNameResponsibilityEntry;
import jetbrains.buildServer.serverSide.*;
import jetbrains.buildServer.serverSide.settings.ProjectSettingsManager;
import jetbrains.buildServer.tests.TestName;
import jetbrains.buildServer.util.StringUtil;
import org.apache.http.HttpStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import slacknotifications.SlackNotification;
import slacknotifications.teamcity.payload.SlackNotificationPayloadManager;
import slacknotifications.teamcity.settings.SlackNotificationConfig;
import slacknotifications.teamcity.settings.SlackNotificationContentConfig;
import slacknotifications.teamcity.settings.SlackNotificationMainSettings;
import slacknotifications.teamcity.settings.SlackNotificationProjectSettings;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* SlackNotificationListner
* Listens for Server events and then triggers the execution of slacknotifications if configured.
*/
public class SlackNotificationListener extends BuildServerAdapter {
private static final String SLACKNOTIFICATIONS_SETTINGS_ATTRIBUTE_NAME = "slackNotifications";
private static final String BUILD_STATE_MESSAGE_END = " at buildState responsibilityChanged";
private static final String BUILD_STATE_MESSAGE_START = "About to process SlackNotifications for ";
private final SBuildServer myBuildServer;
private final ProjectSettingsManager mySettings;
private final SlackNotificationMainSettings myMainSettings;
private final SlackNotificationPayloadManager myManager;
private final SlackNotificationFactory slackNotificationFactory;
private NotificationUtility notificationUtility;
public SlackNotificationListener(){
myBuildServer = null;
mySettings = null;
myMainSettings = null;
myManager = null;
slackNotificationFactory = null;
notificationUtility = new NotificationUtility();
}
public SlackNotificationListener(SBuildServer sBuildServer, ProjectSettingsManager settings,
SlackNotificationMainSettings configSettings, SlackNotificationPayloadManager manager,
SlackNotificationFactory factory) {
myBuildServer = sBuildServer;
mySettings = settings;
myMainSettings = configSettings;
myManager = manager;
slackNotificationFactory = factory;
notificationUtility = new NotificationUtility();
Loggers.SERVER.info("SlackNotificationListener :: Starting");
}
public void register(){
myBuildServer.addListener(this);
Loggers.SERVER.info("SlackNotificationListener :: Registering");
}
public void getFromConfig(SlackNotification slackNotification, SlackNotificationConfig slackNotificationConfig){
slackNotification.setChannel(StringUtil.isEmpty(slackNotificationConfig.getChannel()) ? myMainSettings.getDefaultChannel() : slackNotificationConfig.getChannel());
slackNotification.setTeamName(myMainSettings.getTeamName());
slackNotification.setToken(StringUtil.isEmpty(slackNotificationConfig.getToken()) ? myMainSettings.getToken() : slackNotificationConfig.getToken());
slackNotification.setIconUrl(myMainSettings.getIconUrl());
slackNotification.setBotName(myMainSettings.getBotName());
slackNotification.setEnabled(myMainSettings.getEnabled() && slackNotificationConfig.getEnabled());
slackNotification.setBuildStates(slackNotificationConfig.getBuildStates());
slackNotification.setProxy(myMainSettings.getProxyConfig());
slackNotification.setShowBuildAgent(myMainSettings.getShowBuildAgent());
slackNotification.setShowElapsedBuildTime(myMainSettings.getShowElapsedBuildTime());
slackNotification.setShowCommits(myMainSettings.getShowCommits());
slackNotification.setShowCommitters(myMainSettings.getShowCommitters());
slackNotification.setShowFailureReason(myMainSettings.getShowFailureReason() == null ? SlackNotificationContentConfig.DEFAULT_SHOW_FAILURE_REASON : myMainSettings.getShowFailureReason());
slackNotification.setMaxCommitsToDisplay(myMainSettings.getMaxCommitsToDisplay());
slackNotification.setMentionChannelEnabled(slackNotificationConfig.getMentionChannelEnabled());
slackNotification.setMentionSlackUserEnabled(slackNotificationConfig.getMentionSlackUserEnabled());
slackNotification.setShowElapsedBuildTime(myMainSettings.getShowElapsedBuildTime());
if(slackNotificationConfig.getContent() != null && slackNotificationConfig.getContent().isEnabled()) {
slackNotification.setBotName(slackNotificationConfig.getContent().getBotName());
slackNotification.setIconUrl(slackNotificationConfig.getContent().getIconUrl());
slackNotification.setMaxCommitsToDisplay(slackNotificationConfig.getContent().getMaxCommitsToDisplay());
slackNotification.setShowBuildAgent(slackNotificationConfig.getContent().getShowBuildAgent());
slackNotification.setShowElapsedBuildTime(slackNotificationConfig.getContent().getShowElapsedBuildTime());
slackNotification.setShowCommits(slackNotificationConfig.getContent().getShowCommits());
slackNotification.setShowCommitters(slackNotificationConfig.getContent().getShowCommitters());
slackNotification.setShowFailureReason(slackNotificationConfig.getContent().getShowFailureReason() == null ? SlackNotificationContentConfig.DEFAULT_SHOW_FAILURE_REASON : slackNotificationConfig.getContent().getShowFailureReason());
}
Loggers.ACTIVITIES.debug("SlackNotificationListener :: SlackNotification proxy set to "
+ slackNotification.getProxyHost() + " for " + slackNotificationConfig.getChannel());
}
private void processBuildEvent(SRunningBuild sRunningBuild, BuildStateEnum state) {
Loggers.SERVER.debug("About to process Slack notifications for " + sRunningBuild.getProjectId() + " at buildState " + state.getShortName());
for (SlackNotificationConfigWrapper slackNotificationConfigWrapper : getListOfEnabledSlackNotifications(sRunningBuild.getProjectId())){
if (state.equals(BuildStateEnum.BUILD_STARTED)){
slackNotificationConfigWrapper.slackNotification.setPayload(myManager.buildStarted(sRunningBuild, getPreviousNonPersonalBuild(sRunningBuild)));
slackNotificationConfigWrapper.slackNotification.setEnabled(slackNotificationConfigWrapper.whc.isEnabledForBuildType(sRunningBuild.getBuildType()) && slackNotificationConfigWrapper.slackNotification.getBuildStates().enabled(BuildStateEnum.BUILD_STARTED));
} else if (state.equals(BuildStateEnum.BUILD_INTERRUPTED)){
slackNotificationConfigWrapper.slackNotification.setPayload(myManager.buildInterrupted(sRunningBuild, getPreviousNonPersonalBuild(sRunningBuild)));
slackNotificationConfigWrapper.slackNotification.setEnabled(slackNotificationConfigWrapper.whc.isEnabledForBuildType(sRunningBuild.getBuildType()) && slackNotificationConfigWrapper.slackNotification.getBuildStates().enabled(BuildStateEnum.BUILD_INTERRUPTED));
} else if (state.equals(BuildStateEnum.BEFORE_BUILD_FINISHED)){
slackNotificationConfigWrapper.slackNotification.setPayload(myManager.beforeBuildFinish(sRunningBuild, getPreviousNonPersonalBuild(sRunningBuild)));
slackNotificationConfigWrapper.slackNotification.setEnabled(slackNotificationConfigWrapper.whc.isEnabledForBuildType(sRunningBuild.getBuildType()) && slackNotificationConfigWrapper.slackNotification.getBuildStates().enabled(BuildStateEnum.BEFORE_BUILD_FINISHED));
} else if (state.equals(BuildStateEnum.BUILD_FINISHED)){
slackNotificationConfigWrapper.slackNotification.setEnabled(slackNotificationConfigWrapper.whc.isEnabledForBuildType(sRunningBuild.getBuildType()) && slackNotificationConfigWrapper.slackNotification.getBuildStates().enabled(
BuildStateEnum.BUILD_FINISHED,
sRunningBuild.getStatusDescriptor().isSuccessful(),
this.hasBuildChangedHistoricalState(sRunningBuild)));
slackNotificationConfigWrapper.slackNotification.setPayload(myManager.buildFinished(sRunningBuild, getPreviousNonPersonalBuild(sRunningBuild)));;
}
doPost(slackNotificationConfigWrapper.slackNotification);
//Loggers.ACTIVITIES.debug("SlackNotificationListener :: " + myManager.getFormat(slackNotificationConfigWrapper.whc.getPayloadFormat()).getFormatDescription());
}
}
/**
* Build a list of Enabled SlackNotifications to pass to the POSTing logic.
* @param projectId
* @return
*/
private List<SlackNotificationConfigWrapper> getListOfEnabledSlackNotifications(String projectId) {
List<SlackNotificationConfigWrapper> configs = new ArrayList<SlackNotificationConfigWrapper>();
List<SProject> projects = new ArrayList<SProject>();
SProject myProject = myBuildServer.getProjectManager().findProjectById(projectId);
projects.addAll(myProject.getProjectPath());
for (SProject project : projects){
SlackNotificationProjectSettings projSettings = (SlackNotificationProjectSettings) mySettings.getSettings(project.getProjectId(), SLACKNOTIFICATIONS_SETTINGS_ATTRIBUTE_NAME);
if (projSettings.isEnabled()){
for (SlackNotificationConfig whc : projSettings.getSlackNotificationsConfigs()){
if (whc.isEnabledForSubProjects() == false && !myProject.getProjectId().equals(project.getProjectId())){
// Sub-projects are disabled and we are a subproject.
if (Loggers.ACTIVITIES.isDebugEnabled()){
Loggers.ACTIVITIES.debug(this.getClass().getSimpleName() + ":getListOfEnabledSlackNotifications() "
+ ":: subprojects not enabled. myProject is: " + myProject.getProjectId() + ". slacknotifications project is: " + project.getProjectId());
}
continue;
}
if (whc.getEnabled()){
SlackNotification wh = slackNotificationFactory.getSlackNotification();
this.getFromConfig(wh, whc);
configs.add(new SlackNotificationConfigWrapper(wh, whc));
} else {
Loggers.ACTIVITIES.debug(this.getClass().getSimpleName()
+ ":processBuildEvent() :: SlackNotification disabled. Will not process " + whc.getChannel());
}
}
} else {
Loggers.ACTIVITIES.debug("SlackNotificationListener :: SlackNotifications are disasbled for " + projectId);
}
}
return configs;
}
@Override
public void buildStarted(SRunningBuild sRunningBuild){
processBuildEvent(sRunningBuild, BuildStateEnum.BUILD_STARTED);
}
@Override
public void buildFinished(SRunningBuild sRunningBuild){
processBuildEvent(sRunningBuild, BuildStateEnum.BUILD_FINISHED);
}
@Override
public void buildInterrupted(SRunningBuild sRunningBuild) {
processBuildEvent(sRunningBuild, BuildStateEnum.BUILD_INTERRUPTED);
}
@Override
public void beforeBuildFinish(SRunningBuild sRunningBuild) {
processBuildEvent(sRunningBuild, BuildStateEnum.BEFORE_BUILD_FINISHED);
}
@Deprecated
/** This method has been removed from the TeamCity API as of version 7.1
*
* @param sBuildType
* @param responsibilityInfoOld
* @param responsibilityInfoNew
* @param isUserAction
*/
public void responsibleChanged(@NotNull SBuildType sBuildType,
@NotNull ResponsibilityInfo responsibilityInfoOld,
@NotNull ResponsibilityInfo responsibilityInfoNew,
boolean isUserAction) {
if (myBuildServer.getServerMajorVersion() >= 7){
return;
}
Loggers.SERVER.debug(BUILD_STATE_MESSAGE_START + sBuildType.getProjectId() + BUILD_STATE_MESSAGE_END);
for (SlackNotificationConfigWrapper whcw : getListOfEnabledSlackNotifications(sBuildType.getProjectId())){
//SlackNotificationPayload payloadFormat = myManager.getFormat(whcw.whc.getPayloadFormat());
whcw.slackNotification.setPayload(myManager.responsibleChanged(sBuildType,
responsibilityInfoOld,
responsibilityInfoNew,
isUserAction));
whcw.slackNotification.setEnabled(whcw.whc.isEnabledForBuildType(sBuildType) && whcw.slackNotification.getBuildStates().enabled(BuildStateEnum.RESPONSIBILITY_CHANGED));
doPost(whcw.slackNotification);
//Loggers.ACTIVITIES.debug("SlackNotificationListener :: " + myManager.getFormat(whcw.whc.getPayloadFormat()).getFormatDescription());
}
}
@Override
public void responsibleChanged(SProject project,
Collection<TestName> testNames, ResponsibilityEntry entry,
boolean isUserAction) {
Loggers.SERVER.debug(BUILD_STATE_MESSAGE_START + project.getProjectId() + BUILD_STATE_MESSAGE_END);
for (SlackNotificationConfigWrapper whcw : getListOfEnabledSlackNotifications(project.getProjectId())){
whcw.slackNotification.setPayload(myManager.responsibleChanged(project,
testNames,
entry,
isUserAction));
whcw.slackNotification.setEnabled(whcw.slackNotification.getBuildStates().enabled(BuildStateEnum.RESPONSIBILITY_CHANGED));
doPost(whcw.slackNotification);
//Loggers.ACTIVITIES.debug("SlackNotificationListener :: " + myManager.getFormat(whcw.whc.getPayloadFormat()).getFormatDescription());
}
}
@Override
public void responsibleChanged(SProject project, TestNameResponsibilityEntry oldTestNameResponsibilityEntry, TestNameResponsibilityEntry newTestNameResponsibilityEntry, boolean isUserAction) {
Loggers.SERVER.debug(BUILD_STATE_MESSAGE_START + project.getProjectId() + BUILD_STATE_MESSAGE_END);
for (SlackNotificationConfigWrapper whcw : getListOfEnabledSlackNotifications(project.getProjectId())){
//SlackNotificationPayload payloadFormat = myManager.getFormat(whcw.whc.getPayloadFormat());
whcw.slackNotification.setPayload(myManager.responsibleChanged(project,
oldTestNameResponsibilityEntry,
newTestNameResponsibilityEntry,
isUserAction));
whcw.slackNotification.setEnabled(whcw.slackNotification.getBuildStates().enabled(BuildStateEnum.RESPONSIBILITY_CHANGED));
doPost(whcw.slackNotification);
//Loggers.ACTIVITIES.debug("SlackNotificationListener :: " + myManager.getFormat(whcw.whc.getPayloadFormat()).getFormatDescription());
}
}
/**
* New version of responsibleChanged, which has some bugfixes, but
* is only available in versions 7.0 and above.
* @param sBuildType
* @param responsibilityEntryOld
* @param responsibilityEntryNew
* @since 7.0
*/
@Override
public void responsibleChanged(@NotNull SBuildType sBuildType,
@NotNull ResponsibilityEntry responsibilityEntryOld,
@NotNull ResponsibilityEntry responsibilityEntryNew){
Loggers.SERVER.debug(BUILD_STATE_MESSAGE_START + sBuildType.getProjectId() + BUILD_STATE_MESSAGE_END);
for (SlackNotificationConfigWrapper whcw : getListOfEnabledSlackNotifications(sBuildType.getProjectId())){
//SlackNotificationPayload payloadFormat = myManager.getFormat(whcw.whc.getPayloadFormat());
whcw.slackNotification.setPayload(myManager.responsibleChanged(sBuildType,
responsibilityEntryOld,
responsibilityEntryNew));
whcw.slackNotification.setEnabled(whcw.whc.isEnabledForBuildType(sBuildType) && whcw.slackNotification.getBuildStates().enabled(BuildStateEnum.RESPONSIBILITY_CHANGED));
doPost(whcw.slackNotification);
//Loggers.ACTIVITIES.debug("SlackNotificationListener :: " + myManager.getFormat(whcw.whc.getPayloadFormat()).getFormatDescription());
}
}
@Override
public void responsibleRemoved(SProject project, TestNameResponsibilityEntry entry){
}
/** doPost used by responsibleChanged
*
* @param notification
*/
public void doPost(SlackNotification notification) {
notificationUtility.doPost(notification);
}
@Nullable
private SFinishedBuild getPreviousNonPersonalBuild(SRunningBuild paramSRunningBuild)
{
List<SFinishedBuild> localList = this.myBuildServer.getHistory().getEntriesBefore(paramSRunningBuild, false);
for (SFinishedBuild localSFinishedBuild : localList)
if (!(localSFinishedBuild.isPersonal())) return localSFinishedBuild;
return null;
}
private boolean hasBuildChangedHistoricalState(SRunningBuild sRunningBuild){
SFinishedBuild previous = getPreviousNonPersonalBuild(sRunningBuild);
if (previous != null){
if (sRunningBuild.getBuildStatus().isSuccessful()){
return previous.getBuildStatus().isFailed();
} else if (sRunningBuild.getBuildStatus().isFailed()) {
return previous.getBuildStatus().isSuccessful();
}
}
return true;
}
/**
* An inner class to wrap up the SlackNotification and its SlackNotificationConfig into one unit.
*
*/
private class SlackNotificationConfigWrapper {
private SlackNotification slackNotification;
private SlackNotificationConfig whc;
public SlackNotificationConfigWrapper(SlackNotification slackNotification, SlackNotificationConfig slackNotificationConfig) {
this.slackNotification = slackNotification;
this.whc = slackNotificationConfig;
}
public void setSlackNotification(SlackNotification slackNotification){
this.slackNotification=slackNotification;
}
public SlackNotification getSlackNotification(){
return slackNotification;
}
public void setSlackNotificationConfig(SlackNotificationConfig whc){
this.whc=whc;
}
public SlackNotificationConfig getSlackNotificationConfig(){
return whc;
}
}
}