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

    Details

    • Type: Bug
    • Status: Open (View Workflow)
    • Priority: Minor
    • Resolution: Unresolved
    • Labels:
      None
    • Environment:
      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
    • Similar Issues:

      Description

      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.

        Attachments

          Activity

          Hide
          skinitimski 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.

          Show
          skinitimski 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.
          Hide
          skinitimski 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.

          Show
          skinitimski 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 .
          Hide
          skinitimski 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.

          Show
          skinitimski 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.
          Hide
          tomscommerce 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

          Show
          tomscommerce 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
          Hide
          tomscommerce 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); }

          }
          }

          Show
          tomscommerce 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); } } }
          Hide
          tomscommerce 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.

          Show
          tomscommerce 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.
          Hide
          tomscommerce 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.  

          Show
          tomscommerce 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.  

            People

            • Assignee:
              Unassigned
              Reporter:
              skinitimski Timothy Klopotoski
            • Votes:
              2 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated: