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

Missing root URL in the generated link to build while using groovy-html.template

    • Icon: Bug Bug
    • Resolution: Not A Defect
    • Icon: Major Major
    • email-ext-plugin
    • None
    • Jenkins ver. 2.73.3
      Email-ext vers. 2.62

      I am using the groovy-html.template which generates a link to the build and it is coming up as something like: nulljob/test/2 where test is the name of the job and 2 is the build number. The nulljob part is coming from the concatenation of null root URL and the job URL as can be seen from here:

            <td><A href="${rooturl}${build.url}">${rooturl}${build.url}</A></td> 

      The rooturl property is coming from ExtendedEmailPublisherDescriptor.java.getHudsonUrl() as seen here

              binding.put("rooturl", descriptor.getHudsonUrl()); 

      This method internally just calls Jenkins.getActiveInstance().getRootUrl() as seen here:

          public String getHudsonUrl() {
              return Jenkins.getActiveInstance().getRootUrl();
          } 

       When I run the above API in Jenkins Script Console, I am getting the correct output:

      print(Jenkins.getActiveInstance().getRootUrl()) 

      This means, it is not a global configuration issue and from the code it is not obvious how a null value could creep in.

          [JENKINS-52983] Missing root URL in the generated link to build while using groovy-html.template

          Hari Dara added a comment -

          One may use this simple pipeline script to reproduce the issue:

          pipeline {
              agent any
              
              stages {
                  stage("Init") {
                      steps {
                          sh "exit 1"
                      }
                  }
              }
          
              post {
                  failure {
                      emailext subject: "Pipeline failed: ${currentBuild.fullDisplayName}",
                               body: '''${SCRIPT, template="groovy-html.template"}''',
                               mimeType: 'text/html',
                               recipientProviders: [requestor()],
                               replyTo: 'noreply@salesforce.com'
                  }
              }
          } 

          Hari Dara added a comment - One may use this simple pipeline script to reproduce the issue: pipeline { agent any stages { stage("Init") { steps { sh "exit 1" } } } post { failure { emailext subject: "Pipeline failed: ${currentBuild.fullDisplayName}", body: '''${SCRIPT, template="groovy-html.template"}''', mimeType: 'text/html', recipientProviders: [requestor()], replyTo: 'noreply@salesforce.com' } } }

          Hari Dara added a comment -

          Adding to the puzzle, tried the below script in Script Console and it gave the right URL:

          import hudson.plugins.emailext.ExtendedEmailPublisherDescriptor
          descriptor = Jenkins.getActiveInstance().getDescriptorByType(ExtendedEmailPublisherDescriptor.class)
          print("getHudsonUrl: ${descriptor.getHudsonUrl()}") 

           

          Hari Dara added a comment - Adding to the puzzle, tried the below script in Script Console and it gave the right URL: import hudson.plugins.emailext.ExtendedEmailPublisherDescriptor descriptor = Jenkins.getActiveInstance().getDescriptorByType(ExtendedEmailPublisherDescriptor.class) print("getHudsonUrl: ${descriptor.getHudsonUrl()}")  

          Hari Dara added a comment -

          Using the below pipeline script (I had to disable Groovy Sandbox), I am seeing null values for url:

          import hudson.plugins.emailext.ExtendedEmailPublisherDescriptor;pipeline {
              agent any
              
              stages {
                  stage("Init") {
                      steps {
                          sh "exit 1"
                      }
                  }
              }
              post {
                  failure {
                      script {
                          def descriptor = Jenkins.getActiveInstance().getDescriptorByType(ExtendedEmailPublisherDescriptor.class)
                          echo "getHudsonUrl: ${descriptor.getHudsonUrl()}"
                          echo "getRootUrl: ${Jenkins.getActiveInstance().getRootUrl()}"
                      }
                  }
              }
          } 

          Here is the console output:

          Started by user Hari Krishna Dara
          Running in Durability level: MAX_SURVIVABILITY
          [Pipeline] node
          ...
          [Pipeline] // stage
          [Pipeline] stage
          [Pipeline] { (Declarative: Post Actions)
          [Pipeline] script
          [Pipeline] {
          [Pipeline] echo
          getHudsonUrl: null
          [Pipeline] echo
          getRootUrl: null
          [Pipeline] }
          [Pipeline] // script
          [Pipeline] }
          [Pipeline] // stage
          [Pipeline] }
          [Pipeline] // node
          [Pipeline] End of Pipeline
          ERROR: script returned exit code 1
          Finished: FAILURE 

          What could be different about this context? Now it appears to be even unrelated to email-ext plugin.

          Hari Dara added a comment - Using the below pipeline script (I had to disable Groovy Sandbox), I am seeing null values for url: import hudson.plugins.emailext.ExtendedEmailPublisherDescriptor;pipeline { agent any stages { stage("Init") { steps { sh "exit 1" } } } post { failure { script { def descriptor = Jenkins.getActiveInstance().getDescriptorByType(ExtendedEmailPublisherDescriptor.class) echo "getHudsonUrl: ${descriptor.getHudsonUrl()}" echo "getRootUrl: ${Jenkins.getActiveInstance().getRootUrl()}" } } } } Here is the console output: Started by user Hari Krishna Dara Running in Durability level: MAX_SURVIVABILITY [Pipeline] node ... [Pipeline] // stage [Pipeline] stage [Pipeline] { (Declarative: Post Actions) [Pipeline] script [Pipeline] { [Pipeline] echo getHudsonUrl: null [Pipeline] echo getRootUrl: null [Pipeline] } [Pipeline] // script [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline ERROR: script returned exit code 1 Finished: FAILURE What could be different about this context? Now it appears to be even unrelated to email-ext plugin.

          Hari Dara added a comment -

          The url gets computed from here:

              public @Nullable String getRootUrl() throws IllegalStateException {
                  final JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get();
                  if (config == null) {
                      // Try to get standard message if possible
                      final Jenkins j = Jenkins.getInstance();
                      throw new IllegalStateException("Jenkins instance " + j + " has been successfully initialized, but JenkinsLocationConfiguration is undefined.");
                  }
                  String url = config.getUrl();
                  if(url!=null) {
                      return Util.ensureEndsWith(url,"/");
                  }
                  StaplerRequest req = Stapler.getCurrentRequest();
                  if(req!=null)
                      return getRootUrlFromRequest();
                  return null;
              }
          

          I tried running the below code in Script Console and got null:

          import jenkins.model.JenkinsLocationConfiguration
          config = JenkinsLocationConfiguration.get();
          print(config.getUrl()) 

          However, I do have Jenkins URL configured under Jenkins Location. Why is this coming back as null ?

          Hari Dara added a comment - The url gets computed from  here : public @Nullable String getRootUrl() throws IllegalStateException { final JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get(); if (config == null) { // Try to get standard message if possible final Jenkins j = Jenkins.getInstance(); throw new IllegalStateException("Jenkins instance " + j + " has been successfully initialized, but JenkinsLocationConfiguration is undefined."); } String url = config.getUrl(); if(url!=null) { return Util.ensureEndsWith(url,"/"); } StaplerRequest req = Stapler.getCurrentRequest(); if(req!=null) return getRootUrlFromRequest(); return null; } I tried running the below code in Script Console and got null: import jenkins.model.JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get(); print(config.getUrl()) However, I do have Jenkins URL configured under Jenkins Location . Why is this coming back as null ?

          Hari Dara added a comment -

          To answer my own question on the difference in context, in the case when Jenkins.getRootUrl() works (i.e., from Script Console), though location config returns null, the URL is being extracted from the http request. In case of job execution, there is no such request, so it returns null.

          Hari Dara added a comment - To answer my own question on the difference in context, in the case when Jenkins.getRootUrl()  works (i.e., from Script Console), though location config returns null , the URL is being extracted from the http request. In case of job execution, there is no such request, so it returns null.

          Hari Dara added a comment -

          By comparing the config with another Jenkins instance where this is working, I noticed that the jenkins.model.JenkinsLocationConfiguration.xml file is missing on this environment. When I used the below code, the file got created:

          import jenkins.model.JenkinsLocationConfiguration
          config = JenkinsLocationConfiguration.get();
          config.setUrl("***")
          config.setAdminAddress("***");
          config.save() 

          May be this gets created only when the corresponding field(s) in /configure are updated, but I didn't know that and the fact that correct URL gets shown on that page made me think that it is all good.

          Actually, this environment got fully created via Ansible scripts, which modify specific parts of the configuration using Groovy scripts, so Save button on the /configure page was never hit, which may be why the location config never got created, and if this is the expected behavior, I will have to add the above to the existing Groovy scripts.

          I can now confirm that the emails have the right links, so this issue can be closed.

          Hari Dara added a comment - By comparing the config with another Jenkins instance where this is working, I noticed that the jenkins.model.JenkinsLocationConfiguration.xml  file is missing on this environment. When I used the below code, the file got created: import jenkins.model.JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get(); config.setUrl("***") config.setAdminAddress("***"); config.save() May be this gets created only when the corresponding field(s) in /configure are updated, but I didn't know that and the fact that correct URL gets shown on that page made me think that it is all good. Actually, this environment got fully created via Ansible scripts, which modify specific parts of the configuration using Groovy scripts, so Save button on the /configure page was never hit, which may be why the location config never got created, and if this is the expected behavior, I will have to add the above to the existing Groovy scripts. I can now confirm that the emails have the right links, so this issue can be closed.

          Gregory Fong added a comment -

          I am hitting this issue with the Stash Pullrequest Builder plugin after we upgraded to 2.140, and we do have a jenkins.model.JenkinsLocationConfiguration.xml file.

          Exploring a bit, this plugin uses this code in order to get the URL (see https://github.com/nemccarthy/stash-pullrequest-builder-plugin/blob/master/src/main/java/stashpullrequestbuilder/stashpullrequestbuilder/StashBuilds.java#L52):

                  JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration();
                  String rootUrl = globalConfig.getUrl();
          

          However, I confirmed using script console that this returns null in 2.140:

          JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration();
          println(globalConfig.getUrl());
          

          On the other hand, running the following on script console returns the configured URL as expected:

          config = JenkinsLocationConfiguration.get();
          println(config.getUrl()) 

          It looks like the commit that caused this difference in behavior is: https://github.com/jenkinsci/jenkins/commit/c3988aae8ad66fde529830e799fe3d49cd241ebb#diff-1439884ddd002c6ca6463d9657412b15 .  It seems like constructing a new JenkinsLocationConfiguration no longer invokes the load() method automatically, and I confirmed that calling it manually does result in the configured URL being returned as expected:

          JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration();
          globalConfig.load()
          println(globalConfig.getUrl());
          

          I can't say that what plugins are doing is right or wrong, but the new behavior does break some plugins.  If this is a policy change for plugins, then I can open a bug ticket or submit a pull request for this particular plugin.  But it'd be good to get some feedback from someone who understands the problem better to answer this question: Is this an problem with the plugin's expectations, or is this a Jenkins regression?

          Gregory Fong added a comment - I am hitting this issue with the Stash Pullrequest Builder plugin after we upgraded to 2.140, and we do have a jenkins.model.JenkinsLocationConfiguration.xml file. Exploring a bit, this plugin uses this code in order to get the URL (see https://github.com/nemccarthy/stash-pullrequest-builder-plugin/blob/master/src/main/java/stashpullrequestbuilder/stashpullrequestbuilder/StashBuilds.java#L52 ): JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration(); String rootUrl = globalConfig.getUrl(); However, I confirmed using script console that this returns null in 2.140: JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration(); println(globalConfig.getUrl()); On the other hand, running the following on script console returns the configured URL as expected: config = JenkinsLocationConfiguration.get(); println(config.getUrl()) It looks like the commit that caused this difference in behavior is: https://github.com/jenkinsci/jenkins/commit/c3988aae8ad66fde529830e799fe3d49cd241ebb#diff-1439884ddd002c6ca6463d9657412b15 .  It seems like constructing a new JenkinsLocationConfiguration no longer invokes the load() method automatically, and I confirmed that calling it manually does result in the configured URL being returned as expected: JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration(); globalConfig.load() println(globalConfig.getUrl()); I can't say that what plugins are doing is right or wrong, but the new behavior does break some plugins.  If this is a policy change for plugins, then I can open a bug ticket or submit a pull request for this particular plugin.  But it'd be good to get some feedback from someone who understands the problem better to answer this question: Is this an problem with the plugin's expectations, or is this a Jenkins regression?

          Alex Earl added a comment -

          There was no defect in email-ext itself.

          Alex Earl added a comment - There was no defect in email-ext itself.

            Unassigned Unassigned
            haridsv Hari Dara
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: