diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/template/AbstractFileSetBasedWebHookTemplate.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/template/AbstractFileSetBasedWebHookTemplate.java new file mode 100644 index 00000000..3e189ab0 --- /dev/null +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/template/AbstractFileSetBasedWebHookTemplate.java @@ -0,0 +1,183 @@ +package webhook.teamcity.payload.template; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import webhook.teamcity.BuildStateEnum; +import webhook.teamcity.Loggers; +import webhook.teamcity.payload.WebHookTemplateContent; +import webhook.teamcity.payload.WebHookTemplateManager; + +public abstract class AbstractFileSetBasedWebHookTemplate extends AbstractWebHookTemplate { + + Map templateContent = new HashMap(); + Map branchTemplateContent = new HashMap(); + + public abstract String getLoggingName(); + public abstract String getTemplateFilesLocation(); + + public abstract Map getNormalTemplateMap(); + public abstract Map getBranchTemplateMap(); + + public AbstractFileSetBasedWebHookTemplate(WebHookTemplateManager manager) { + setTemplateManager(manager); + } + + @Override + public void register() { + templateContent.clear(); + branchTemplateContent.clear(); + loadTemplatesFromFileSet(); + if (!templateContent.isEmpty() && !branchTemplateContent.isEmpty()){ + super.register(this); + } else { + if (templateContent.isEmpty()){ + Loggers.SERVER.error(getLoggingName() + " :: Failed to register template " + getTemplateShortName() + ". No regular template configurations were found."); + } + if (branchTemplateContent.isEmpty()){ + Loggers.SERVER.error(getLoggingName() + " :: Failed to register template " + getTemplateShortName() + ". No branch template configurations were found."); + } + } + } + + private URL findPropertiesFileUrlInVariousClassloaders(String propertiesFile) { + final ClassLoader[] classLoaders = {AbstractFileSetBasedWebHookTemplate.class.getClassLoader(), ClassLoader.getSystemClassLoader()}; + URL url = null; + for (ClassLoader cl : classLoaders){ + if (cl != null){ + url = cl.getResource(propertiesFile); + if (url != null){ + break; + } + } + } + return url; + } + + private static final int BUFFER_SIZE = 4 * 1024; + + public static String inputStreamToString(InputStream inputStream, String charsetName) + throws IOException { + StringBuilder builder = new StringBuilder(); + InputStreamReader reader = new InputStreamReader(inputStream, charsetName); + char[] buffer = new char[BUFFER_SIZE]; + int length; + while ((length = reader.read(buffer)) != -1) { + builder.append(buffer, 0, length); + } + return builder.toString(); + } + + public String readFully(InputStream inputStream, String encoding) + throws IOException { + return new String(readFully(inputStream), encoding); + } + + private byte[] readFully(InputStream inputStream) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, length); + } + return baos.toByteArray(); + } + + private String readFileContents(String templateFile){ + URL url = findPropertiesFileUrlInVariousClassloaders(templateFile); + if (url != null) { + InputStream in = null; + try { + in = url.openStream(); + return inputStreamToString(in, "UTF-8"); + } catch (IOException e) { + Loggers.SERVER.error(getLoggingName() + " :: An Error occurred trying to load the template file: " + templateFile + "."); + Loggers.SERVER.debug(e); + + } finally { + try { + if (in != null){ + in.close(); + } + } catch (IOException e) { + Loggers.SERVER.error(e); + } + } + } else { + Loggers.SERVER.error(getLoggingName() + " :: An Error occurred trying to load the template file: " + templateFile + ". The file was not found in the classpath."); + } + return null; + } + + /** + * Load the template from a file, rather than doing silly string escaping in java. + */ + private void loadTemplatesFromFileSet() { + for (BuildStateEnum state : BuildStateEnum.getNotifyStates()){ + if (getNormalTemplateMap().containsKey(state)){ + String templateContents = readFileContents(getTemplateFilesLocation() + File.separator + getNormalTemplateMap().get(state)); + if (templateContents != null) { + templateContent.put(state, WebHookTemplateContent.create( + state.getShortName(), + templateContents, + true, + this.getPreferredDateTimeFormat())); + Loggers.SERVER.info(getLoggingName() + " :: Found and loaded normal template for: " + state.getShortName()); + Loggers.SERVER.debug(getLoggingName() + " :: Template content is: " + templateContents); + + } + } + } + for (BuildStateEnum state : BuildStateEnum.getNotifyStates()){ + if (getBranchTemplateMap().containsKey(state)){ + String templateContents = readFileContents(getTemplateFilesLocation() + File.separator + getBranchTemplateMap().get(state)); + if (templateContents != null) { + branchTemplateContent.put(state, WebHookTemplateContent.create( + state.getShortName(), + templateContents, + true, + this.getPreferredDateTimeFormat())); + Loggers.SERVER.info(getLoggingName() + " :: Found and loaded branch template for: " + state.getShortName()); + Loggers.SERVER.debug(getLoggingName() + " :: Template content is: " + templateContents); + + } + } + } + } + + @Override + public WebHookTemplateContent getTemplateForState(BuildStateEnum buildState) { + if (templateContent.containsKey(buildState)){ + return (templateContent.get(buildState)).copy(); + } + return null; + } + + @Override + public WebHookTemplateContent getBranchTemplateForState(BuildStateEnum buildState) { + if (branchTemplateContent.containsKey(buildState)){ + return (branchTemplateContent.get(buildState)).copy(); + } + return null; + } + + @Override + public Set getSupportedBuildStates() { + return templateContent.keySet(); + } + + @Override + public Set getSupportedBranchBuildStates() { + return branchTemplateContent.keySet(); + } + +} diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/template/FlowdockWebHookTemplate.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/template/FlowdockWebHookTemplate.java new file mode 100644 index 00000000..8308a063 --- /dev/null +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/template/FlowdockWebHookTemplate.java @@ -0,0 +1,81 @@ +package webhook.teamcity.payload.template; + +import java.util.Map; +import java.util.TreeMap; + +import webhook.teamcity.BuildStateEnum; +import webhook.teamcity.payload.WebHookTemplateManager; +import webhook.teamcity.payload.format.WebHookPayloadJsonTemplate; + +public class FlowdockWebHookTemplate extends AbstractFileSetBasedWebHookTemplate { + + private Map normalTemplateMap = new TreeMap(); + private Map branchTemplateMap = new TreeMap(); + + public FlowdockWebHookTemplate(WebHookTemplateManager manager) { + super(manager); + + normalTemplateMap.put(BuildStateEnum.BUILD_BROKEN, "flowdock-buildBroken-buildFailed-buildInterrupted-normal.json"); + normalTemplateMap.put(BuildStateEnum.BUILD_FAILED, "flowdock-buildBroken-buildFailed-buildInterrupted-normal.json"); + normalTemplateMap.put(BuildStateEnum.BUILD_INTERRUPTED, "flowdock-buildBroken-buildFailed-buildInterrupted-normal.json"); + normalTemplateMap.put(BuildStateEnum.BUILD_STARTED, "flowdock-buildStarted-normal.json"); + normalTemplateMap.put(BuildStateEnum.BUILD_SUCCESSFUL, "flowdock-buildSuccessful-buildFixed-normal.json"); + normalTemplateMap.put(BuildStateEnum.BUILD_FIXED, "flowdock-buildSuccessful-buildFixed-normal.json"); + normalTemplateMap.put(BuildStateEnum.RESPONSIBILITY_CHANGED, "flowdock-responsibilityChanged-normal.json"); + + branchTemplateMap.put(BuildStateEnum.BUILD_BROKEN, "flowdock-buildBroken-buildFailed-buildInterrupted-branch.json"); + branchTemplateMap.put(BuildStateEnum.BUILD_FAILED, "flowdock-buildBroken-buildFailed-buildInterrupted-branch.json"); + branchTemplateMap.put(BuildStateEnum.BUILD_INTERRUPTED, "flowdock-buildBroken-buildFailed-buildInterrupted-branch.json"); + branchTemplateMap.put(BuildStateEnum.BUILD_STARTED, "flowdock-buildStarted-branch.json"); + branchTemplateMap.put(BuildStateEnum.BUILD_SUCCESSFUL, "flowdock-buildSuccessful-buildFixed-branch.json"); + branchTemplateMap.put(BuildStateEnum.BUILD_FIXED, "flowdock-buildSuccessful-buildFixed-branch.json"); + branchTemplateMap.put(BuildStateEnum.RESPONSIBILITY_CHANGED, "flowdock-responsibilityChanged-branch.json"); + + } + + @Override + public String getTemplateDescription() { + return "Flowdock JSON templates"; + } + + @Override + public String getTemplateToolTipText() { + return "Supports the TeamCity Flowdock JSON integration"; + } + + @Override + public String getTemplateShortName() { + return "flowdock"; + } + + @Override + public boolean supportsPayloadFormat(String payloadFormat) { + return payloadFormat.equalsIgnoreCase(WebHookPayloadJsonTemplate.FORMAT_SHORT_NAME); + } + + @Override + public String getPreferredDateTimeFormat() { + return ""; + } + + @Override + public String getLoggingName() { + return this.getClass().getSimpleName(); + } + + @Override + public String getTemplateFilesLocation() { + return "webhook/teamcity/payload/template/flowdock"; + } + + @Override + public Map getNormalTemplateMap() { + return this.normalTemplateMap; + } + + @Override + public Map getBranchTemplateMap() { + return this.branchTemplateMap; + } + +} diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/StringSanitiser.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/StringSanitiser.java new file mode 100644 index 00000000..620cc03b --- /dev/null +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/StringSanitiser.java @@ -0,0 +1,27 @@ +package webhook.teamcity.payload.util; + +public class StringSanitiser { + + public static String sanitise(String dirtyString) { + return dirtyString + .replace("<", "_") + .replace(">", "_") + .replace("\\", "_") + .replace("/", "_") + .replace("$", "_") + .replace("%", "_") + .replace("#", "_") + .replace("@", "_") + .replace("!", "_") + .replace("`", "_") + .replace("~", "_") + .replace("?", "_") + .replace("|", "_") + .replace("*", "_") + .replace("(", "_") + .replace(")", "_") + .replace("^", "_") + ; + } + +} diff --git a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/WebHooksBeanUtilsVariableResolver.java b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/WebHooksBeanUtilsVariableResolver.java index 77495aa8..dccb64f2 100644 --- a/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/WebHooksBeanUtilsVariableResolver.java +++ b/tcwebhooks-core/src/main/java/webhook/teamcity/payload/util/WebHooksBeanUtilsVariableResolver.java @@ -47,7 +47,24 @@ public String resolve(String variableName) { // do nothing and let the logic below handle it. } } + + if ((variableName.startsWith("sanitise(") || variableName.startsWith("sanitize(")) && variableName.endsWith(")")){ + try { + String dirtyString = variableName.substring("sanitise(".length(), variableName.length() - ")".length()); + if (teamcityProperties.containsKey(dirtyString)){ + return StringSanitiser.sanitise(teamcityProperties.get(dirtyString)); + } else { + return StringSanitiser.sanitise((String) PropertyUtils.getProperty(bean, dirtyString).toString()); + } + // do nothing and let the logic below handle it. + } catch (NullPointerException npe){ + } catch (IllegalArgumentException iae){ + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } catch (NoSuchMethodException e) { + } + } try { // Try getting it from teamcity first. diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildBroken-buildFailed-buildInterrupted-branch.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildBroken-buildFailed-buildInterrupted-branch.json new file mode 100644 index 00000000..3e3961db --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildBroken-buildFailed-buildInterrupted-branch.json @@ -0,0 +1,26 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/buildFailed%402x.png" + }, + "title": "updated build status to ${notifyType}", + "external_thread_id": "teamcity-${buildName}-${sanitize(branchName)}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#${branchDisplayName}", "#teamcity" ], + "thread": { + "title": "${buildName} (${sanitize(branchDisplayName)})", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Branch", "value": "${branchDisplayName}" }, + { "label": "Default Branch", "value": "${branchIsDefault}" }, + { "label": "Triggered By", "value": "${triggeredBy}" }, + { "label": "Agent", "value": "${agentName}" } + ], + "body": "${projectName} :: ${buildName} # ${buildNumber} has ${buildStateDescription} with a status of ${buildResult}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "red", + "value": "${buildResult}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildBroken-buildFailed-buildInterrupted-normal.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildBroken-buildFailed-buildInterrupted-normal.json new file mode 100644 index 00000000..6330b332 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildBroken-buildFailed-buildInterrupted-normal.json @@ -0,0 +1,24 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/buildFailed%402x.png" + }, + "title": "updated build status to ${notifyType}", + "external_thread_id": "teamcity-${buildName}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#teamcity" ], + "thread": { + "title": "${buildName}", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Triggered By", "value": "${triggeredBy}" }, + { "label": "Agent", "value": "${agentName}" } + ], + "body": "${projectName} :: ${buildName} # ${buildNumber} has ${buildStateDescription} with a status of ${buildResult}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "red", + "value": "${buildResult}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildStarted-branch.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildStarted-branch.json new file mode 100644 index 00000000..32737742 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildStarted-branch.json @@ -0,0 +1,26 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/FFFFFF-0.png" + }, + "title": "updated build status to ${notifyType}", + "external_thread_id": "teamcity-${buildName}-${sanitize(branchName)}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#${branchDisplayName}", "#teamcity" ], + "thread": { + "title": "${buildName} (${sanitize(branchDisplayName)})", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Branch", "value": "${branchDisplayName}" }, + { "label": "Default Branch", "value": "${branchIsDefault}" }, + { "label": "Triggered By", "value": "${triggeredBy}" }, + { "label": "Agent", "value": "${agentName}" } + ], + "body": "${projectName} :: ${buildName} # ${buildNumber} has ${buildStateDescription} with a status of ${buildResult}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "grey", + "value": "${notifyType}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildStarted-normal.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildStarted-normal.json new file mode 100644 index 00000000..f3d721b6 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildStarted-normal.json @@ -0,0 +1,24 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/FFFFFF-0.png" + }, + "title": "updated build status to ${notifyType}", + "external_thread_id": "teamcity-${buildName}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#teamcity" ], + "thread": { + "title": "${buildName}", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Triggered By", "value": "${triggeredBy}" }, + { "label": "Agent", "value": "${agentName}" } + ], + "body": "${projectName} :: ${buildName} # ${buildNumber} has ${buildStateDescription} with a status of ${buildResult}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "grey", + "value": "${notifyType}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildSuccessful-buildFixed-branch.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildSuccessful-buildFixed-branch.json new file mode 100644 index 00000000..fb0aea58 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildSuccessful-buildFixed-branch.json @@ -0,0 +1,26 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/buildSuccessful%402x.png" + }, + "title": "updated build status to ${buildResult}", + "external_thread_id": "teamcity-${buildName}-${sanitize(branchName)}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${buildResult}", "#${branchDisplayName}", "#teamcity" ], + "thread": { + "title": "${buildName} (${sanitize(branchDisplayName)})", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Branch", "value": "${branchDisplayName}" }, + { "label": "Default Branch", "value": "${branchIsDefault}" }, + { "label": "Triggered By", "value": "${triggeredBy}" }, + { "label": "Agent", "value": "${agentName}" } + ], + "body": "${projectName} :: ${buildName} # ${buildNumber} has ${buildStateDescription} with a status of ${buildResult}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "green", + "value": "${buildResult}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildSuccessful-buildFixed-normal.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildSuccessful-buildFixed-normal.json new file mode 100644 index 00000000..8ac56f75 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-buildSuccessful-buildFixed-normal.json @@ -0,0 +1,24 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/buildSuccessful%402x.png" + }, + "title": "updated build status to ${buildResult}", + "external_thread_id": "teamcity-${buildName}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${buildResult}", "#teamcity" ], + "thread": { + "title": "${buildName}", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Triggered By", "value": "${triggeredBy}" }, + { "label": "Agent", "value": "${agentName}" } + ], + "body": "${projectName} :: ${buildName} # ${buildNumber} has ${buildStateDescription} with a status of ${buildResult}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "green", + "value": "${buildResult}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-responsibilityChanged-branch.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-responsibilityChanged-branch.json new file mode 100644 index 00000000..3a7014c1 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-responsibilityChanged-branch.json @@ -0,0 +1,26 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/FFFFFF-0.png" + }, + "title": "updated build status to ${notifyType}", + "external_thread_id": "teamcity-${buildName}-${sanitize(branchName)}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#${branchDisplayName}", "#teamcity" ], + "thread": { + "title": "${buildName} (${sanitize(branchDisplayName)})", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Branch", "value": "${branchDisplayName}" }, + { "label": "Default Branch", "value": "${branchIsDefault}" }, + { "label": "Responsibility", "value": "${responsibilityUserNew} (was: ${responsibilityUserOld})" }, + { "label": "Comment", "value": "${comment}" } + ], + "body": "${projectName} :: ${buildName} has ${buildStateDescription}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "grey", + "value": "${notifyType}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-responsibilityChanged-normal.json b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-responsibilityChanged-normal.json new file mode 100644 index 00000000..dad31352 --- /dev/null +++ b/tcwebhooks-core/src/main/resources/webhook/teamcity/payload/template/flowdock/flowdock-responsibilityChanged-normal.json @@ -0,0 +1,24 @@ +{ + "event": "activity", + "author": { + "name": "TeamCity", + "avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/FFFFFF-0.png" + }, + "title": "updated build status to ${notifyType}", + "external_thread_id": "teamcity-${buildName}", + "tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#teamcity" ], + "thread": { + "title": "${buildName}", + "fields": [ + { "label": "Build", "value": "${buildFullName}" }, + { "label": "Responsibility", "value": "${responsibilityUserNew} (was: ${responsibilityUserOld})" }, + { "label": "Comment", "value": "${comment}" } + ], + "body": "${projectName} :: ${buildName} has ${buildStateDescription}", + "external_url": "${buildStatusUrl}", + "status": { + "color": "grey", + "value": "${notifyType}" + } + } +} \ No newline at end of file diff --git a/tcwebhooks-core/src/test/java/webhook/teamcity/payload/util/VariableMessageBuilderTest.java b/tcwebhooks-core/src/test/java/webhook/teamcity/payload/util/VariableMessageBuilderTest.java index ed81b07b..6f4a36b3 100644 --- a/tcwebhooks-core/src/test/java/webhook/teamcity/payload/util/VariableMessageBuilderTest.java +++ b/tcwebhooks-core/src/test/java/webhook/teamcity/payload/util/VariableMessageBuilderTest.java @@ -40,6 +40,7 @@ public void setup(){ teamcityProperties.put("env.isInATest", "Yes, we are in a test"); teamcityProperties.put("buildFullName", "Hopefully will never see this."); teamcityProperties.put("buildFullName", "Hopefully will never see this."); + teamcityProperties.put("someTagThing", "A ~peice of text! with <> s<< + + 4.0.0 + + netwolfuk.teamcity.plugins.tcwebhooks + tcwebhooks + ${currentVersion} + + netwolfuk.teamcity.plugins.tcwebhooks + tcwebhooks-template-builder + ${currentVersion} + tcwebhooks-template-builder + http://maven.apache.org + + UTF-8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + + netwolfuk.teamcity.plugins.tcwebhooks + tcwebhooks-core + ${currentVersion} + compile + + + + netwolfuk.teamcity.plugins.tcwebhooks + tcwebhooks-core + ${currentVersion} + test-jar + test + + + + org.jetbrains.teamcity + server-api + ${teamcityVersion} + test + + + + commons-io + commons-io + 2.4 + + + + org.apache.commons + commons-lang3 + 3.4 + + + + com.squareup + javapoet + 1.3.0 + + + + junit + junit + 3.8.1 + test + + + + org.mockito + mockito-all + 1.8.0 + test + + + + diff --git a/tcwebhooks-template-builder/src/main/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/TemplateClassBuilder.java b/tcwebhooks-template-builder/src/main/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/TemplateClassBuilder.java new file mode 100644 index 00000000..96011eb5 --- /dev/null +++ b/tcwebhooks-template-builder/src/main/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/TemplateClassBuilder.java @@ -0,0 +1,37 @@ +package netwolfuk.teamcity.plugins.tcwebhooks.template.builder; + +import java.io.File; +import java.io.IOException; + +import javax.lang.model.element.Modifier; + +import org.apache.commons.lang3.StringUtils; + +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; + +public class TemplateClassBuilder { + + public void build(String templateName, String targetFileLocation) throws IOException{ + String camelName = StringUtils.capitalize(templateName); + FieldSpec android = FieldSpec.builder(String.class, "android") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + + TypeSpec helloWorld = TypeSpec.classBuilder(camelName) + .addModifiers(Modifier.PUBLIC) + .addField(android) + .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL) + .build(); + + JavaFile javaFile = JavaFile.builder("webhook.teamcity.payload.template", helloWorld) + .build(); + + + File classFile = new File(targetFileLocation); + + javaFile.writeTo(classFile); + } +} + diff --git a/tcwebhooks-template-builder/src/main/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/TemplateGenerator.java b/tcwebhooks-template-builder/src/main/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/TemplateGenerator.java new file mode 100644 index 00000000..f0863d6d --- /dev/null +++ b/tcwebhooks-template-builder/src/main/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/TemplateGenerator.java @@ -0,0 +1,67 @@ +package netwolfuk.teamcity.plugins.tcwebhooks.template.builder; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import javax.xml.bind.JAXBException; + +import org.apache.commons.io.FileUtils; + +import webhook.teamcity.settings.entity.WebHookTemplate; +import webhook.teamcity.settings.entity.WebHookTemplate.WebHookTemplateItem; +import webhook.teamcity.settings.entity.WebHookTemplate.WebHookTemplateState; +import webhook.teamcity.settings.entity.WebHookTemplateJaxHelper; +import webhook.teamcity.settings.entity.WebHookTemplates; + +public class TemplateGenerator { + + /** + * Takes an XML file describing a template and generates a bunch of JSON + * files representing the various templates for various build states. + * + * See BuildFlowDockTemplates() method in the {@link BuildFlowDockTemplateFiles} test + * for an example usage. + * + * @param templateName + * @param templateFileLocation + * @param targetFileLocation + * @throws JAXBException + * @throws IOException + */ + public void generate(String templateName, String templateFileLocation, String targetFileLocation) throws JAXBException, IOException{ + WebHookTemplates templatesList = WebHookTemplateJaxHelper.read(templateFileLocation); + + for (WebHookTemplate template : templatesList.getWebHookTemplateList()){ + if (template.isEnabled() && template.getName().equals(templateName)){ + + File defaultTemplateFile = new File(targetFileLocation + "/" + templateName + "-default-normal.json"); + File defaultBranchTemplateFile = new File(targetFileLocation + "/" + templateName + "-default-branch.json"); + if (template.getDefaultTemplate() != null){ + FileUtils.writeStringToFile(defaultTemplateFile, template.getDefaultTemplate().trim()); + } + if (template.getDefaultBranchTemplate() != null){ + FileUtils.writeStringToFile(defaultBranchTemplateFile, template.getDefaultBranchTemplate().trim()); + } + + for (WebHookTemplateItem item : template.getTemplates()){ + String templateFileName = buildFileName(item.getStates()); + File templateFile = new File(targetFileLocation + "/" + templateName + "-"+ templateFileName +"-normal.json"); + File branchTemplateFile = new File(targetFileLocation + "/" + templateName + "-"+ templateFileName +"-branch.json"); + + FileUtils.writeStringToFile(templateFile, item.getTemplateText().trim()); + FileUtils.writeStringToFile(branchTemplateFile, item.getBranchTemplateText().trim()); + } + } + } + } + + protected String buildFileName(List states) { + StringBuilder filename = new StringBuilder(); + for (WebHookTemplateState state: states){ + filename.append("-").append(state.getType()); + } + return filename.substring(1); + } +} + diff --git a/tcwebhooks-template-builder/src/test/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/BuildFlowDockTemplateFiles.java b/tcwebhooks-template-builder/src/test/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/BuildFlowDockTemplateFiles.java new file mode 100644 index 00000000..b7de1602 --- /dev/null +++ b/tcwebhooks-template-builder/src/test/java/netwolfuk/teamcity/plugins/tcwebhooks/template/builder/BuildFlowDockTemplateFiles.java @@ -0,0 +1,81 @@ +package netwolfuk.teamcity.plugins.tcwebhooks.template.builder; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import javax.xml.bind.JAXBException; + +import jetbrains.buildServer.serverSide.SBuildServer; + +import org.junit.Test; + +import webhook.teamcity.BuildStateEnum; +import webhook.teamcity.payload.WebHookPayloadManager; +import webhook.teamcity.payload.WebHookTemplate; +import webhook.teamcity.payload.WebHookTemplateManager; +import webhook.teamcity.payload.template.FlowdockWebHookTemplate; +import webhook.teamcity.payload.template.WebHookTemplateFromXml; +import webhook.teamcity.settings.entity.WebHookTemplateJaxHelper; +import webhook.teamcity.settings.entity.WebHookTemplates; + + + +public class BuildFlowDockTemplateFiles extends TemplateGenerator { + + private static final String XML_TEMPLATES_FILE = "../tcwebhooks-core/src/test/resources/webhook-templates-flowdock-example.xml"; + + /** + * This Test just builds template files from the contents of an XML template config. + * Note, the output from this test is not automatically validated in the next test. + * The resulting files output in this test must be copied to the correct path before they are tested. + * @throws JAXBException + * @throws IOException + */ + @Test + public void BuildFlowDockTemplates() throws JAXBException, IOException { + TemplateGenerator generator = new TemplateGenerator(); + generator.generate("flowdock", XML_TEMPLATES_FILE, "target"); + + } + + /** + * Validates that the json files in the classpath have the same contents as the XML configuration version of the template. + * @throws FileNotFoundException + * @throws JAXBException + */ + @Test + public void CompareXmlAndSpringTemplates() throws FileNotFoundException, JAXBException{ + SBuildServer sBuildServer = mock(SBuildServer.class); + WebHookPayloadManager webHookPayloadManager = new WebHookPayloadManager(sBuildServer); + WebHookTemplateManager springManager = new WebHookTemplateManager(null, webHookPayloadManager ); + FlowdockWebHookTemplate springTemplate = new FlowdockWebHookTemplate(springManager); + springTemplate.register(); + + WebHookTemplateManager xmlManager = new WebHookTemplateManager(null, webHookPayloadManager ); + + + WebHookTemplates templatesList = WebHookTemplateJaxHelper.read(XML_TEMPLATES_FILE); + for (webhook.teamcity.settings.entity.WebHookTemplate template : templatesList.getWebHookTemplateList()){ + xmlManager.registerTemplateFormatFromXmlConfig(WebHookTemplateFromXml.build(template, webHookPayloadManager)); + } + + WebHookTemplate springFlowTemplate = springManager.getTemplate("flowdock"); + WebHookTemplate xmlFlowTemplate = xmlManager.getTemplate("flowdock"); + + for (BuildStateEnum state : BuildStateEnum.getNotifyStates()){ + if (springFlowTemplate.getSupportedBuildStates().contains(state) || xmlFlowTemplate.getSupportedBuildStates().contains(state)){ + System.out.println(state.getShortName()); + assertEquals(state.getShortName() + " template should match", springFlowTemplate.getTemplateForState(state).getTemplateText().trim(), xmlFlowTemplate.getTemplateForState(state).getTemplateText().trim()); + assertEquals(state.getShortName() + " branch template should match", springFlowTemplate.getBranchTemplateForState(state).getTemplateText().trim(), xmlFlowTemplate.getBranchTemplateForState(state).getTemplateText().trim()); + } + } + + } + + + + +} diff --git a/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml b/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml index f79dbac0..a8bff169 100644 --- a/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml +++ b/tcwebhooks-web-ui/src/main/resources/META-INF/build-server-plugin-WebHookListener.xml @@ -148,5 +148,13 @@ 15 + + + + 14 + +