[JIRA] (JENKINS-60457) NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

12 views
Skip to first unread message

andrea.giardini@camunda.com (JIRA)

unread,
Dec 12, 2019, 4:06:03 AM12/12/19
to jenkinsc...@googlegroups.com
Andrea Giardini created an issue
 
Jenkins / Bug JENKINS-60457
NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate
Issue Type: Bug Bug
Assignee: Alex Earl
Components: email-ext-plugin
Created: 2019-12-12 09:05
Labels: email-ext plugin
Priority: Minor Minor
Reporter: Andrea Giardini

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

Add Comment Add Comment
 
This message was sent by Atlassian Jira (v7.13.6#713006-sha1:cc4451f)
Atlassian logo

andrea.giardini@camunda.com (JIRA)

unread,
Dec 16, 2019, 6:17:03 AM12/16/19
to jenkinsc...@googlegroups.com
Andrea Giardini commented on Bug JENKINS-60457
 
Re: NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

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

slide.o.mix@gmail.com (JIRA)

unread,
Dec 16, 2019, 10:07:03 AM12/16/19
to jenkinsc...@googlegroups.com
Alex Earl commented on Bug JENKINS-60457
 
Re: NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

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

andrea.giardini@camunda.com (JIRA)

unread,
Dec 17, 2019, 1:42:04 AM12/17/19
to jenkinsc...@googlegroups.com
Andrea Giardini commented on Bug JENKINS-60457
 
Re: NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

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>'

slide.o.mix@gmail.com (JIRA)

unread,
Dec 17, 2019, 12:07:03 PM12/17/19
to jenkinsc...@googlegroups.com
Alex Earl commented on Bug JENKINS-60457
 
Re: NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

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

andrea.giardini@camunda.com (JIRA)

unread,
Dec 19, 2019, 1:47:03 AM12/19/19
to jenkinsc...@googlegroups.com
Andrea Giardini commented on Bug JENKINS-60457
 
Re: NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

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()]
  )
} 

 

slide.o.mix@gmail.com (JIRA)

unread,
Dec 19, 2019, 7:36:02 AM12/19/19
to jenkinsc...@googlegroups.com
Alex Earl commented on Bug JENKINS-60457
 
Re: NullPointerException in h.p.e.p.content.ScriptContent.renderTemplate

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

Reply all
Reply to author
Forward
0 new messages