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

"No such DSL method 'call'" when calling step within dynamically loaded library a second time

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Minor Minor
    • None
    • Operating System: amazon linux 64-bit (master+workers)
      Jenkins version: 2.176.2 -- master image is built from official Jenkins LTS Docker image
      workflow-cps-plugin: 2.74
      workflow-cps-global-lib-plugin: 2.15

      My team manages a large shared pipeline library, and I'm working on breaking it up. Recently we migrated to new Jenkins infrastructure and I found that my project was no longer working.

      To demonstrate the problem, I have a global parent library P which dynamically loads child library C. P is globally configured and loaded explicitly in Jenkinsfile J using `@Library()`.

      J --> P --> C

      C contains steps X and Y; X calls Y twice. (see stepFromChild.groovy)

      P contains step Z which loads C, calls Y twice, then calls X (see fail.groovy).

      J imports P and calls Z.

      When J runs, Z calls Y twice successfully.

      But when Z calls X (which calls Y twice), the second call to Y results in "java.lang.NoSuchMethodError: No such DSL method 'call' found among steps ..." (even though globals very clearly includes the called methods – see consoleText-failing.txt).

      On an older Jenkins (2.121.1, workflow-cps 2.58 workflow-global-lib 2.9), I see no failure (see attached log consoleText-sucess.txt)

      So far I have found no workaround to this problem.

      Steps to reproduce, given a Jenkins instance with a valid GitHub credential and plugins installed:

      1. Configure a global library in "Manage Jenkins" called 'timski', pointing at https://github.com/skinitimski/library.parent
      2. Create a Pipeline job with the following pipeline script:

      @Library('timski@master') _

      env.SECRET_ID = 'github-credential-id' // TODO: replace this with the id of a valid GitHub credential

      fail()

      3. Run the job and observe a failure.

          [JENKINS-59337] "No such DSL method 'call'" when calling step within dynamically loaded library a second time

          It is worth noting that there are other ways to produce the issue, but above was the most trivial way I could find so far.

          Timothy Klopotoski added a comment - It is worth noting that there are other ways to produce the issue, but above was the most trivial way I could find so far.

          If I modify the Jenkinsfile to import the parent and the child library, so that the dynamic import is redundant (i.e, it prints "Only using first definition of library child"), everything works.

          So, this problem seems scoped to libraries dynamically loaded using the `library` step.

          Timothy Klopotoski added a comment - If I modify the Jenkinsfile to import the parent and the child library, so that the dynamic import is redundant (i.e, it prints "Only using first definition of library child"), everything works. So, this problem seems scoped to libraries dynamically loaded using the `library` step .

          Timothy Klopotoski added a comment - - edited

          We've found a workaround to this problem.

          As a convention, with extensibility in mind, my team always uses the following signature when writing custom steps:

          def call(Map args) {

          This allows us to add optional step parameters later. We use this even for parameterless steps, in case we want to accept new parameters later on.

          The workaround is to change the signature to explicitly accept no parameters (`def call() {`). This results in no errors.

          It still seems to me that using the named args signature should work.

          Timothy Klopotoski added a comment - - edited We've found a workaround to this problem. As a convention, with extensibility in mind, my team always uses the following signature when writing custom steps : def call(Map args) { This allows us to add optional step parameters later. We use this even for parameterless steps, in case we want to accept new parameters later on. The workaround is to change the signature to explicitly accept no parameters (`def call() {`). This results in no errors. It still seems to me that using the named args signature should work.

          Tom Turner added a comment -

          My work recently updated to 2.190.3.2 and I can confirm duplicate calls to the same declarative function can break shared libraries.  I ended up with the same workaround of removing the argument in the call() method but also needed to do another change.

          Changing

          def call(body) {

          to

          def call() {

          The other change involved removing an apostrophe in an echo. 

          echo "an apostrophe's apostrophe causes DSL failure"

          Attached is the stack trace.jenkins-dsl-error.txt

          Tom Turner added a comment - My work recently updated to 2.190.3.2 and I can confirm duplicate calls to the same declarative function can break shared libraries.  I ended up with the same workaround of removing the argument in the call() method but also needed to do another change. Changing def call(body) { to def call() { The other change involved removing an apostrophe in an echo.  echo "an apostrophe's apostrophe causes DSL failure" Attached is the stack trace. jenkins-dsl-error.txt

          Tom Turner added a comment -

          I took a look at the stack trace and the "call" seems to emanate from here, line 134

          https://github.com/jenkinsci/script-security-plugin/blob/master/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptor.java#L128-L137

          The if NOT probably returns false and then the word "call" is passed.

          // Allow calling closure variables from a script binding as methods
          if (receiver instanceof Script) {
          Script s = (Script) receiver;
          if (s.getBinding().hasVariable(method)) {
          Object var = s.getBinding().getVariable(method);
          if (!InvokerHelper.getMetaClass(var).respondsTo(var, "call", (Object[]) args).isEmpty())

          { return onMethodCall(invoker, var, "call", args); }

          }
          }

          Tom Turner added a comment - I took a look at the stack trace and the "call" seems to emanate from here, line 134 https://github.com/jenkinsci/script-security-plugin/blob/master/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptor.java#L128-L137 The if NOT probably returns false and then the word "call" is passed. // Allow calling closure variables from a script binding as methods if (receiver instanceof Script) { Script s = (Script) receiver; if (s.getBinding().hasVariable(method)) { Object var = s.getBinding().getVariable(method); if (!InvokerHelper.getMetaClass(var).respondsTo(var, "call", (Object[]) args).isEmpty()) { return onMethodCall(invoker, var, "call", args); } } }

          Tom Turner added a comment -

          We received this from Jenkins Support

          Can you check to make sure you are loading this Shared Library implicitly as being referred to within the documentation above or explicitly by making a call using the @Library function in your Script?

          Also, from the snippet being presented it appears that the Call method is attempting to be setup within the Groovy Script itself as detailed in the method presented in the documentation linked here: https://jenkins.io/doc/book/pipeline/shared-libraries/#defining-custom-steps

          The Call method is noticeably malformed, please put the entire method declaration on the same line with the def and also body should be initialized with a type such as String or Closure.

          With that noted it is best practices for both Support to most efficiently answer your questions as well as from your user point of view in managing tickets and keeping track of their history to keep questions/issues to a 1-to-1 ratio per ticket.

          Because of that please do feel free to open another ticket to review this Shared Library error if it is still occurring after this information is used by also uploading the Jenkinsfile/Pipeline Script from the example job that is failing with this error.

          Tom Turner added a comment - We received this from Jenkins Support Can you check to make sure you are loading this Shared Library implicitly as being referred to within the documentation above or explicitly by making a call using the @Library function in your Script? Also, from the snippet being presented it appears that the Call method is attempting to be setup within the Groovy Script itself as detailed in the method presented in the documentation linked here:  https://jenkins.io/doc/book/pipeline/shared-libraries/#defining-custom-steps The Call method is noticeably malformed, please put the entire method declaration on the same line with the def and also body should be initialized with a type such as String or Closure. With that noted it is best practices for both Support to most efficiently answer your questions as well as from your user point of view in managing tickets and keeping track of their history to keep questions/issues to a 1-to-1 ratio per ticket. Because of that please do feel free to open another ticket to review this Shared Library error if it is still occurring after this information is used by also uploading the Jenkinsfile/Pipeline Script from the example job that is failing with this error.

          Tom Turner added a comment -

          To answer the above question, we load the shared libraries explicitly in the Jenkinsfile in the first line, like this.

          @Library(['devkit-jenkins-lib@master','DCT-jenkins-shared@develop']) _

           

          The DSL method was defined like this (based on examples in this page, about 6 months ago.  https://jenkins.io/doc/book/pipeline/shared-libraries/)

          //var/isNPM.groovy

          def call(body) {

          I also tried:

          def call(Closure body) {

          and

          def call(String body) {

          but only this works

          def call() {

           

          Thankfully, my DSL method doesn't require any arguments passed to it.

           

          Note that calls to this isNPM() method work fine throughout the script.  It is only after making some other calls in the flow (to some shell scripts), that subsequent calls to isNPM() fail.  

          Tom Turner added a comment - To answer the above question, we load the shared libraries explicitly in the Jenkinsfile in the first line, like this. @Library( ['devkit-jenkins-lib@master','DCT-jenkins-shared@develop'] ) _   The DSL method was defined like this (based on examples in this page, about 6 months ago.   https://jenkins.io/doc/book/pipeline/shared-libraries/ ) //var/isNPM.groovy def call(body) { I also tried: def call(Closure body) { and def call(String body) { but only this works def call() {   Thankfully, my DSL method doesn't require any arguments passed to it.   Note that calls to this isNPM() method work fine throughout the script.  It is only after making some other calls in the flow (to some shell scripts), that subsequent calls to isNPM() fail.  

          we have a usecase where my DSL requires an argument to be passed.  here import_sharedlib_core DSL imports a core library dynamically and then invokes the method defined there -utils_core.GetEnvVariable(variableName)

          e.g.
          def GetEnvVariable(String variableName) 

          {     import_sharedlib_core()     return utils_core.GetEnvVariable(variableName) }

          When the GetEnvVariable DSL is invoked multiple times in my pipeline it randomly fails with following error -
           
          [2021-05-05T00:19:25.747Z] importing sharedlib_core libary
          [2021-05-05T00:19:25.765Z] Only using first definition of library abisharedlib_core
          groovy.lang.MissingPropertyException: No such property: utils_core for class: utils
           

          Pratibha Natani added a comment - we have a usecase where my DSL requires an argument to be passed.  here import_sharedlib_core DSL imports a core library dynamically and then invokes the method defined there -utils_core.GetEnvVariable(variableName) e.g. def GetEnvVariable(String variableName)  {     import_sharedlib_core()     return utils_core.GetEnvVariable(variableName) } When the GetEnvVariable DSL is invoked multiple times in my pipeline it randomly fails with following error -   [2021-05-05T00:19:25.747Z] importing sharedlib_core libary [2021-05-05T00:19:25.765Z] Only using first definition of library abisharedlib_core groovy.lang.MissingPropertyException: No such property: utils_core for class: utils  

            Unassigned Unassigned
            skinitimski Timothy Klopotoski
            Votes:
            3 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: