Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-60457

NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

    XMLWordPrintable

    Details

    • Similar Issues:

      Description

      I have not managed to reproduce this bug consistently, it looks to me like a race condition somewhere.

      When the build finishes, instead of getting a proper email, we are receving an email containing the following stack trace:

      Exception raised during template rendering: Cannot invoke method getParent() on null object java.lang.NullPointerException: Cannot invoke method getParent() on null object at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:91) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:48) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:35) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at hudson.model.Queue$Executable$getParent.call(Unknown Source) at SimpleTemplateScript14$_run_closure4.doCall(SimpleTemplateScript14.groovy:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at groovy.lang.Closure.call(Closure.java:414) at groovy.lang.Closure.call(Closure.java:430) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2040) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2025) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2066) at org.codehaus.groovy.runtime.dgm$163.invoke(Unknown Source) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) at SimpleTemplateScript14.run(SimpleTemplateScript14.groovy:33) at groovy.text.SimpleTemplateEngine$SimpleTemplate$1.writeTo(SimpleTemplateEngine.java:168) at groovy.text.SimpleTemplateEngine$SimpleTemplate$1.toString(SimpleTemplateEngine.java:180) at hudson.plugins.emailext.plugins.content.ScriptContent.renderTemplate(ScriptContent.java:151) at hudson.plugins.emailext.plugins.content.ScriptContent.evaluate(ScriptContent.java:81) at hudson.plugins.emailext.plugins.content.AbstractEvalContent.evaluate(AbstractEvalContent.java:76) at org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro.evaluate(DataBoundTokenMacro.java:202) at org.jenkinsci.plugins.tokenmacro.Parser.processToken(Parser.java:323) at org.jenkinsci.plugins.tokenmacro.Action$KiHW1UeqOdqAwZul.run(Unknown Source) at org.parboiled.matchers.ActionMatcher.match(ActionMatcher.java:96) at org.parboiled.parserunners.BasicParseRunner.match(BasicParseRunner.java:77) at org.parboiled.MatcherContext.runMatcher(MatcherContext.java:351) at org.parboiled.matchers.SequenceMatcher.match(SequenceMatcher.java:46) at org.parboiled.parserunners.BasicParseRunner.match(BasicParseRunner.java:77) at org.parboiled.MatcherContext.runMatcher(MatcherContext.java:351) at org.parboiled.matchers.FirstOfMatcher.match(FirstOfMatcher.java:41) at org.parboiled.parserunners.BasicParseRunner.match(BasicParseRunner.java:77) at org.parboiled.MatcherContext.runMatcher(MatcherContext.java:351) at org.parboiled.matchers.FirstOfMatcher.match(FirstOfMatcher.java:41) at org.parboiled.parserunners.BasicParseRunner.match(BasicParseRunner.java:77) at org.parboiled.MatcherContext.runMatcher(MatcherContext.java:351) at org.parboiled.matchers.ZeroOrMoreMatcher.match(ZeroOrMoreMatcher.java:39) at org.parboiled.parserunners.BasicParseRunner.match(BasicParseRunner.java:77) at org.parboiled.MatcherContext.runMatcher(MatcherContext.java:351) at org.parboiled.matchers.SequenceMatcher.match(SequenceMatcher.java:46) at org.parboiled.parserunners.BasicParseRunner.match(BasicParseRunner.java:77) at org.parboiled.MatcherContext.runMatcher(MatcherContext.java:351) at org.parboiled.parserunners.BasicParseRunner.run(BasicParseRunner.java:72) at org.parboiled.parserunners.ReportingParseRunner.runBasicMatch(ReportingParseRunner.java:86) at org.parboiled.parserunners.ReportingParseRunner.run(ReportingParseRunner.java:66) at org.parboiled.parserunners.AbstractParseRunner.run(AbstractParseRunner.java:81) at org.parboiled.parserunners.AbstractParseRunner.run(AbstractParseRunner.java:76) at org.jenkinsci.plugins.tokenmacro.Parser.process(Parser.java:85) at org.jenkinsci.plugins.tokenmacro.Parser.process(Parser.java:74) at org.jenkinsci.plugins.tokenmacro.TokenMacro.expand(TokenMacro.java:199) at org.jenkinsci.plugins.tokenmacro.TokenMacro.expandAll(TokenMacro.java:237) at hudson.plugins.emailext.plugins.ContentBuilder.transformText(ContentBuilder.java:80) at hudson.plugins.emailext.ExtendedEmailPublisher.addContent(ExtendedEmailPublisher.java:880) at hudson.plugins.emailext.ExtendedEmailPublisher.createMail(ExtendedEmailPublisher.java:753) at hudson.plugins.emailext.ExtendedEmailPublisher.sendMail(ExtendedEmailPublisher.java:451) at hudson.plugins.emailext.ExtendedEmailPublisher._perform(ExtendedEmailPublisher.java:441) at hudson.plugins.emailext.ExtendedEmailPublisher.perform(ExtendedEmailPublisher.java:349) at hudson.tasks.BuildStepMonitor$1.perform(BuildStepMonitor.java:20) at hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:741) at hudson.model.AbstractBuild$AbstractBuildExecution.performAllBuildSteps(AbstractBuild.java:690) at hudson.model.Build$BuildExecution.cleanUp(Build.java:196) at hudson.model.Run.execute(Run.java:1862) at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:43) at hudson.model.ResourceController.execute(ResourceController.java:97) at hudson.model.Executor.run(Executor.java:429)
      

      We use:
      Jenkins ver. 2.190.1
      Email extension plugin : 2.68

        Attachments

          Activity

          andrea_giardini Andrea Giardini created issue -
          Hide
          andrea_giardini Andrea Giardini added a comment -

          I forgot to mention that this is quite annoying for us because the email with the stacktrace is sent to every contributor of our project
          Looks like every email that is part of the git history is included

          Show
          andrea_giardini Andrea Giardini added a comment - I forgot to mention that this is quite annoying for us because the email with the stacktrace is sent to every contributor of our project Looks like every email that is part of the git history is included
          Hide
          slide_o_mix Alex Earl added a comment -

          What template are you using? What does your config look like? 

          Show
          slide_o_mix Alex Earl added a comment - What template are you using? What does your config look like? 
          Hide
          andrea_giardini Andrea Giardini added a comment -

          Hello Alex and thank you for your answer

          This is the template we are using:

          <%
          /**
           * injected usable variables:
           * https://github.com/jenkinsci/email-ext-plugin/blob/master/src/main/java/hudson/plugins/emailext/plugins/content/ScriptContent.java#L120-L125
           */
          
          import hudson.model.Result
          import org.jenkinsci.plugins.tokenmacro.TokenMacro;
          
          def getBuildCauses = { c, sc ->
            if (c instanceof hudson.model.Cause.UpstreamCause) {
              sc.add(c)
              c.upstreamCauses.each { upstreamCause ->
                owner.call(upstreamCause, sc)
              }
            } else if (c instanceof java.util.Collection) {
              c.each { cause ->
                owner.call(cause, sc)
              }
            } else {
              // just add it
              sc.add(c)
            }
          }
          
          def buildCauses = []
          getBuildCauses(build.causes, buildCauses)
          buildCauses = buildCauses.reverse()
          
          def upstreamBuilds = buildCauses.findAll{ it instanceof hudson.model.Cause.UpstreamCause }
            .collect{ return it.getUpstreamRun() }
          def changeSetsByBuild = [:]
          upstreamBuilds.each{
            changeSetsByBuild.put(
              it.getParent().name,
              ['changesets': it.changeSets, 'buildurl': rooturl + it.getUrl()]
            )
          }
          changeSetsByBuild.put(
            project.name,
            ['changesets': build.changeSets, 'buildurl': rooturl + build.getUrl()]
          )
          %>
          <STYLE>
            BODY TABLE, TD, TH, P, H1, H2 { margin:0; font:normal normal 100% Georgia, Serif; background-color: #ffffff; }
          H1, H2 { border-bottom:dotted 1px #999999; padding:5px; margin-top:10px; margin-bottom:10px; color: #000000; font: normal bold 130% Georgia,Serif; background-color:#f0f0f0; }
          H2 { padding:5px; margin-top:5px; margin-bottom:5px; font: italic bold 110% Georgia,Serif; }
            .bg2 { color:black; background-color:#E0E0E0; font-size:110%; }
          TH { font-weight: bold; }
          TR, TD, TH { padding:2px; }
          TR.gray { background-color:#f0f0f0; }
          TD.test_passed { color:blue; }
          TD.test_failed { color:red; }
          TD.test_skipped { color:grey; }
            .console { font: normal normal 90% Courier New, monotype; padding:0px; margin:0px; }
          DIV.content, DIV.header { background: #ffffff; border: line; 1px #666; margin: 2px; content: 2px; padding: 2px; }
          TABLE.border, TH.border, TD.border { border: 1px solid black; border-collapse:collapse; }
          TD.right { text-align:right; }
          </STYLE>
          <BODY>
              <DIV class="header">
              <!-- GENERAL INFO -->
                  <TABLE width="100%">
                      <TH><TR><TD colspan="2" valign="center"><H2><IMG SRC="${rooturl}static/e59dfe28/images/24x24/<%= (build.result == null || build.result == Result.SUCCESS) ? 'blue.gif' : build.result == Result.FAILURE ? 'red.gif' : 'yellow.gif' %>" /><B>BUILD ${build.result}</B></TD></TR></TH>
          <TR><TD>URL</TD><TD><A href="${rooturl}${build.url}">${rooturl}${build.url}</A></TD></TR>
          <TR><TD>Project:</TD><TD>${project.name}</TD></TR>
                      <TR><TD>Git SHA:</TD><TD><%= TokenMacro.expand( build, listener, '${GIT_REVISION} on branch ${GIT_BRANCH}' ) %></TD></TR>
            <TR><TD>Date:</TD><TD>${it.timestampString}</TD></TR>
                      <TR><TD>Duration:</TD><TD>${build.durationString}</TD></TR>
            <TR>
            <TD>Build causes:</TD>
                          <TD>
                          <% buildCauses.each { buildCause -> %>
                          ${buildCause.shortDescription}<BR/>
            <% } %>
          </TD>
                      </TR>
            <TR><TD>Built on node:</TD><TD><%= build.builtOnStr == '' ? 'master' : build.builtOnStr %></TD></TD></TR>
          </TABLE>
              </DIV>
          
            <% if(changeSetsByBuild) { %>
            <!-- CHANGE SETS -->
              <DIV class="content">
            <H2>Changes</H2>
                  <TABLE width="100%">
                  <%  changeSetsByBuild.each() { projectName, values -> %>
                      <TR><TD class="bg2" colspan="2"><B>Job: <A href="${values['buildurl']}">${projectName}</B></a></TD></TR>
                      <%
                          def changeSet = values['changesets'][0] ?: null
                          if(changeSet != null && !changeSet.isEmptySet()) {
                              changeSet.each { cs ->
                                  commitId = cs.id
                                  author = cs.author
                                  commitMsg = cs.msgAnnotated
                                  commitUrl = changeSet.getBrowser().getChangeSetLink(cs)
                      %>
                      <TR><TD colspan="2">&nbsp;&nbsp;<B>${commitMsg}</B> - <A href="${commitUrl}" target="_blank">${commitId}</A> by <%= author %></TD></TR>
                      <%      }
                          }
                          else {
                      %>
                      <TR><TD colspan="2">No Changes</TD></TR>
                      <%
                          }
                      }
                      %>
                  </TABLE>
            </DIV>
          <% } %>
          
          <% if (build.result != Result.SUCCESS) { %>
              <!-- BUILD FAILURE ANALYZER -->
              <DIV class="content">
                  <H2>Build Failure Analyzer</H2>
            <%= TokenMacro.expand(build, listener, '${BUILD_FAILURE_ANALYZER, includeTitle=false, includeIndications=true, useHtmlFormat=true, noFailureText="No build failure cause detected."}') %>
            </DIV>
          <% } %>
          
          <% if (build.result != Result.SUCCESS && !it.JUnitTestResult.isEmpty()) { %>
              <!-- JUnit TEMPLATE -->
              <DIV class="content">
                  <A href="${rooturl}${build.url}/testreport"><H2>Failed JUnit Tests</H2></A>
            <TABLE class="border">
            <TR>
            <TH class="border">Package</TH>
                          <TH class="border">Failed</TH>
            <TH class="border">Passed</TH>
                          <TH class="border">Skipped</TH>
            <TH class="border">Total</TH>
                      </TR>
            <% it.JUnitTestResult.each { junitResult -> %>
            <% junitResult.getChildren().each { packageResult -> %>
            <% if (packageResult.getFailCount() > 0) { %>
            <TR>
            <TD class="border"><TT>${packageResult.getName()}</TT></TD>
            <TD class="border test_failed">${packageResult.getFailCount()}</TD>
                          <TD class="border test_passed">${packageResult.getPassCount()}</TD>
            <TD class="border test_skipped">${packageResult.getSkipCount()}</TD>
                          <TD class="border"><B>${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}</B></TD>
                      </TR>
            <% packageResult.getFailedTests().each { failed_test -> %>
            <TR><TD class="test_failed" colspan="5"><TT>${failed_test.getFullName()}</TT></TD></TR>
                                  <% } %>
                              <% } %>
                          <% } %>
                      <% } %>
                  </TABLE>
            <BR />
            </DIV>
          <% } %>
          
          <% if (build.result != Result.SUCCESS) { %>
              <!-- CONSOLE OUTPUT -->
              <DIV class="content">
                  <A href="${rooturl}${build.url}/console"><H2>Console Output</H2></A>
            <TABLE class="console">
            <% build.getLog(50).each { line -> %>
            <TR><TD><TT>${org.apache.commons.lang.StringEscapeUtils.escapeHtml(line)}</TT></TD></TR>
                  <% } %>
                  </TABLE>
            <BR />
            </DIV>
          <% } %>
          
          <% if (build.result != Result.SUCCESS && build.artifacts && build.artifacts.size() > 0) { %>
              <!-- ARTIFACTS -->
              <DIV class="content">
                  <H2>Build Artifacts</H2>
            <UL>
            <% build.artifacts.each { artifact -> %>
            <LI><A href="${rooturl}${build.url}artifact/${artifact}">${artifact}</A></LI>
            <% } %>
            </UL>
              </DIV>
            <% } %>
            </BODY>
          

          It's added to the config files using Jobdsl:

          configFiles {
            groovyTemplateConfig {
              id('build-notification-mail-groovy')
              name('build-notification-mail-groovy')
              comment('Build notification mail template written in Groovy')
              content(readFileFromWorkspace('configfiles/emailext/build_notification_template.groovy'))
            }
          }
          

          And this is the CasC configuration:

          unclassified:
            extendedemailpublisher:
              adminRequiredForTemplateTesting: true
              defaultBody: '^${SCRIPT,template="managed:build-notification-mail-groovy"}'
              defaultPresendScript: |
                def failureCauses = build.actions?.find{ it instanceof com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseBuildAction }
          
                if (failureCauses) {
                    def retryBuild = build.actions?.find{ it instanceof com.chikli.hudson.plugin.naginator.NaginatorAction }
          
                    if (retryBuild && retryBuild.getRetryCount() < retryBuild.getMaxRetryCount()) {
                        cancel = true
                        return
                    }
          
                    if (!retryBuild) {
                        // if no retryBuild, check if failureCauses contains the BFA retries,
                        // if true, we assume it is the first build where the error occurs because there won't be a NaginatorAction attached to the build
                        for (def ffc in failureCauses.getFoundFailureCauses()) {
                            if (ffc.categories.find { it ==~ /(Infrastructure|RFT)+/ }) {
                                cancel = true
                                return
                            }
                        }
                    }
                }
              defaultReplyTo: ''
              defaultSubject: '[$PROJECT_NAME] - $BUILD_STATUS - Build #$BUILD_NUMBER'
              listId: 'Build Notifications <ci.cloud>'
          
          Show
          andrea_giardini Andrea Giardini added a comment - Hello Alex and thank you for your answer This is the template we are using: <% /** * injected usable variables: * https: //github.com/jenkinsci/email-ext-plugin/blob/master/src/main/java/hudson/plugins/emailext/plugins/content/ScriptContent.java#L120-L125 */ import hudson.model.Result import org.jenkinsci.plugins.tokenmacro.TokenMacro; def getBuildCauses = { c, sc -> if (c instanceof hudson.model.Cause.UpstreamCause) { sc.add(c) c.upstreamCauses.each { upstreamCause -> owner.call(upstreamCause, sc) } } else if (c instanceof java.util.Collection) { c.each { cause -> owner.call(cause, sc) } } else { // just add it sc.add(c) } } def buildCauses = [] getBuildCauses(build.causes, buildCauses) buildCauses = buildCauses.reverse() def upstreamBuilds = buildCauses.findAll{ it instanceof hudson.model.Cause.UpstreamCause } .collect{ return it.getUpstreamRun() } def changeSetsByBuild = [:] upstreamBuilds.each{ changeSetsByBuild.put( it.getParent().name, [ 'changesets' : it.changeSets, 'buildurl' : rooturl + it.getUrl()] ) } changeSetsByBuild.put( project.name, [ 'changesets' : build.changeSets, 'buildurl' : rooturl + build.getUrl()] ) %> <STYLE> BODY TABLE, TD, TH, P, H1, H2 { margin:0; font:normal normal 100% Georgia, Serif; background-color: #ffffff; } H1, H2 { border-bottom:dotted 1px #999999; padding:5px; margin-top:10px; margin-bottom:10px; color: #000000; font: normal bold 130% Georgia,Serif; background-color:#f0f0f0; } H2 { padding:5px; margin-top:5px; margin-bottom:5px; font: italic bold 110% Georgia,Serif; } .bg2 { color:black; background-color:#E0E0E0; font-size:110%; } TH { font-weight: bold; } TR, TD, TH { padding:2px; } TR.gray { background-color:#f0f0f0; } TD.test_passed { color:blue; } TD.test_failed { color:red; } TD.test_skipped { color:grey; } .console { font: normal normal 90% Courier New, monotype; padding:0px; margin:0px; } DIV.content, DIV.header { background: #ffffff; border: line; 1px #666; margin: 2px; content: 2px; padding: 2px; } TABLE.border, TH.border, TD.border { border: 1px solid black; border-collapse:collapse; } TD.right { text-align:right; } </STYLE> <BODY> <DIV class= "header" > <!-- GENERAL INFO --> <TABLE width= "100%" > <TH><TR><TD colspan= "2" valign= "center" ><H2><IMG SRC= "${rooturl} static /e59dfe28/images/24x24/<%= (build.result == null || build.result == Result.SUCCESS) ? 'blue.gif' : build.result == Result.FAILURE ? 'red.gif' : 'yellow.gif' %>" /><B>BUILD ${build.result}</B></TD></TR></TH> <TR><TD>URL</TD><TD><A href= "${rooturl}${build.url}" >${rooturl}${build.url}</A></TD></TR> <TR><TD>Project:</TD><TD>${project.name}</TD></TR> <TR><TD>Git SHA:</TD><TD><%= TokenMacro.expand( build, listener, '${GIT_REVISION} on branch ${GIT_BRANCH}' ) %></TD></TR> <TR><TD>Date:</TD><TD>${it.timestampString}</TD></TR> <TR><TD>Duration:</TD><TD>${build.durationString}</TD></TR> <TR> <TD>Build causes:</TD> <TD> <% buildCauses.each { buildCause -> %> ${buildCause.shortDescription}<BR/> <% } %> </TD> </TR> <TR><TD>Built on node:</TD><TD><%= build.builtOnStr == '' ? ' master' : build.builtOnStr %></TD></TD></TR> </TABLE> </DIV> <% if (changeSetsByBuild) { %> <!-- CHANGE SETS --> <DIV class= "content" > <H2>Changes</H2> <TABLE width= "100%" > <% changeSetsByBuild.each() { projectName, values -> %> <TR><TD class= "bg2" colspan= "2" ><B>Job: <A href= "${values[ 'buildurl' ]}" >${projectName}</B></a></TD></TR> <% def changeSet = values[ 'changesets' ][0] ?: null if (changeSet != null && !changeSet.isEmptySet()) { changeSet.each { cs -> commitId = cs.id author = cs.author commitMsg = cs.msgAnnotated commitUrl = changeSet.getBrowser().getChangeSetLink(cs) %> <TR><TD colspan= "2" >&nbsp;&nbsp;<B>${commitMsg}</B> - <A href= "${commitUrl}" target= "_blank" >${commitId}</A> by <%= author %></TD></TR> <% } } else { %> <TR><TD colspan= "2" >No Changes</TD></TR> <% } } %> </TABLE> </DIV> <% } %> <% if (build.result != Result.SUCCESS) { %> <!-- BUILD FAILURE ANALYZER --> <DIV class= "content" > <H2>Build Failure Analyzer</H2> <%= TokenMacro.expand(build, listener, '${BUILD_FAILURE_ANALYZER, includeTitle= false , includeIndications= true , useHtmlFormat= true , noFailureText= "No build failure cause detected." }' ) %> </DIV> <% } %> <% if (build.result != Result.SUCCESS && !it.JUnitTestResult.isEmpty()) { %> <!-- JUnit TEMPLATE --> <DIV class= "content" > <A href= "${rooturl}${build.url}/testreport" ><H2>Failed JUnit Tests</H2></A> <TABLE class= "border" > <TR> <TH class= "border" >Package</TH> <TH class= "border" >Failed</TH> <TH class= "border" >Passed</TH> <TH class= "border" >Skipped</TH> <TH class= "border" >Total</TH> </TR> <% it.JUnitTestResult.each { junitResult -> %> <% junitResult.getChildren().each { packageResult -> %> <% if (packageResult.getFailCount() > 0) { %> <TR> <TD class= "border" ><TT>${packageResult.getName()}</TT></TD> <TD class= "border test_failed" >${packageResult.getFailCount()}</TD> <TD class= "border test_passed" >${packageResult.getPassCount()}</TD> <TD class= "border test_skipped" >${packageResult.getSkipCount()}</TD> <TD class= "border" ><B>${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}</B></TD> </TR> <% packageResult.getFailedTests().each { failed_test -> %> <TR><TD class= "test_failed" colspan= "5" ><TT>${failed_test.getFullName()}</TT></TD></TR> <% } %> <% } %> <% } %> <% } %> </TABLE> <BR /> </DIV> <% } %> <% if (build.result != Result.SUCCESS) { %> <!-- CONSOLE OUTPUT --> <DIV class= "content" > <A href= "${rooturl}${build.url}/console" ><H2>Console Output</H2></A> <TABLE class= "console" > <% build.getLog(50).each { line -> %> <TR><TD><TT>${org.apache.commons.lang.StringEscapeUtils.escapeHtml(line)}</TT></TD></TR> <% } %> </TABLE> <BR /> </DIV> <% } %> <% if (build.result != Result.SUCCESS && build.artifacts && build.artifacts.size() > 0) { %> <!-- ARTIFACTS --> <DIV class= "content" > <H2>Build Artifacts</H2> <UL> <% build.artifacts.each { artifact -> %> <LI><A href= "${rooturl}${build.url}artifact/${artifact}" >${artifact}</A></LI> <% } %> </UL> </DIV> <% } %> </BODY> It's added to the config files using Jobdsl: configFiles { groovyTemplateConfig { id( 'build-notification-mail-groovy' ) name( 'build-notification-mail-groovy' ) comment( 'Build notification mail template written in Groovy' ) content(readFileFromWorkspace( 'configfiles/emailext/build_notification_template.groovy' )) } } And this is the CasC configuration: unclassified: extendedemailpublisher: adminRequiredForTemplateTesting: true defaultBody: '^${SCRIPT,template= "managed:build-notification-mail-groovy" }' defaultPresendScript: | def failureCauses = build.actions?.find{ it instanceof com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseBuildAction } if (failureCauses) { def retryBuild = build.actions?.find{ it instanceof com.chikli.hudson.plugin.naginator.NaginatorAction } if (retryBuild && retryBuild.getRetryCount() < retryBuild.getMaxRetryCount()) { cancel = true return } if (!retryBuild) { // if no retryBuild, check if failureCauses contains the BFA retries, // if true , we assume it is the first build where the error occurs because there won't be a NaginatorAction attached to the build for (def ffc in failureCauses.getFoundFailureCauses()) { if (ffc.categories.find { it ==~ /(Infrastructure|RFT)+/ }) { cancel = true return } } } } defaultReplyTo: '' defaultSubject: '[$PROJECT_NAME] - $BUILD_STATUS - Build #$BUILD_NUMBER' listId: 'Build Notifications <ci.cloud>'
          Hide
          slide_o_mix Alex Earl added a comment -

          You don't need to use token macro to expand those tokens. You can just use the tokens like functions in the template in a groovy context <%= %> and it will be used correctly

          Show
          slide_o_mix Alex Earl added a comment - You don't need to use token macro to expand those tokens. You can just use the tokens like functions in the template in a groovy context <%= %> and it will be used correctly
          Hide
          andrea_giardini Andrea Giardini added a comment -

          Thank you for helping out Do you think that's the problem?

          I was thinking about adding a null-check in:

          upstreamBuilds.each{
            changeSetsByBuild.put(
              it.getParent().name,
              ['changesets': it.changeSets, 'buildurl': rooturl + it.getUrl()]
            )
          } 

           

          Show
          andrea_giardini Andrea Giardini added a comment - Thank you for helping out Do you think that's the problem? I was thinking about adding a null-check in: upstreamBuilds.each{ changeSetsByBuild.put( it.getParent().name, [ 'changesets' : it.changeSets, 'buildurl' : rooturl + it.getUrl()] ) }  
          Hide
          slide_o_mix Alex Earl added a comment -

          No, I don't think it's the problem. Adding a null check would be a good idea.

          Show
          slide_o_mix Alex Earl added a comment - No, I don't think it's the problem. Adding a null check would be a good idea.
          Hide
          slide_o_mix Alex Earl added a comment -

          Did you ever get this resolved?

          Show
          slide_o_mix Alex Earl added a comment - Did you ever get this resolved?
          Hide
          andrea_giardini Andrea Giardini added a comment -

          Hi Alex

          I believe changing the template did the trick. We replaced the code above with:

          Map changeSetsByBuild = upstreamBuilds.grep().collectEntries {
            [(it.getParent().name): ['changesets': it.changeSets, 'buildurl': rooturl + it.getUrl()]]
          }

          Since then we didn't have another NPE triggered. But this was always difficult to reproduce so I can't be 100% sure.

          Does this make sense to you?

          Show
          andrea_giardini Andrea Giardini added a comment - Hi Alex I believe changing the template did the trick. We replaced the code above with: Map changeSetsByBuild = upstreamBuilds.grep().collectEntries { [(it.getParent().name): [ 'changesets' : it.changeSets, 'buildurl' : rooturl + it.getUrl()]] } Since then we didn't have another NPE triggered. But this was always difficult to reproduce so I can't be 100% sure. Does this make sense to you?
          Hide
          slide_o_mix Alex Earl added a comment -

          The addition of the grep will basically filter out any null items. So, it does make sense. I'm not sure why there would be null items in the upstreamBuilds collection though. Do you feel ok closing this out?

          Show
          slide_o_mix Alex Earl added a comment - The addition of the grep will basically filter out any null items. So, it does make sense. I'm not sure why there would be null items in the upstreamBuilds collection though. Do you feel ok closing this out?
          Hide
          andrea_giardini Andrea Giardini added a comment -

          Makes sense, please close the issue and thank you for the feedback!

          Show
          andrea_giardini Andrea Giardini added a comment - Makes sense, please close the issue and thank you for the feedback!
          slide_o_mix Alex Earl made changes -
          Field Original Value New Value
          Resolution Not A Defect [ 7 ]
          Status Open [ 1 ] Resolved [ 5 ]

            People

            Assignee:
            slide_o_mix Alex Earl
            Reporter:
            andrea_giardini Andrea Giardini
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Dates

              Created:
              Updated:
              Resolved: