Uploaded image for project: 'Jenkins Website'
  1. Jenkins Website
  2. WEBSITE-321

Script in "Defining global variables" on "Extending with Shared Libraries" does not work

    Details

    • Type: Bug
    • Status: To Do (View Workflow)
    • Priority: Minor
    • Resolution: Unresolved
    • Component/s: content
    • Labels:
      None
    • Similar Issues:

      Description

      See https://jenkins.io/doc/book/pipeline/shared-libraries/#defining-global-variables

      "vars/acme.groovy"
      #!/usr/bin/env groovy
      // vars/acme.groovy
      class acme implements Serializable {
          private String name
          def setName(value) {
              name = value
          }
          def getName() {
              name
          }
          def caution(message) {
              echo "Hello, ${name}! CAUTION: ${message}" // line 12
          }
      }
      

      The Pipeline can then invoke these methods which will be defined on the acme object:

      acme.name = 'Alice'
      echo acme.name /* prints: 'Alice' */
      acme.caution 'The queen is angry!' /* prints: 'Hello, Alice. CAUTION: The queen is angry!' */
      

      This fails with:

      hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: acme.echo() is applicable for argument types: (org.codehaus.groovy.runtime.GStringImpl) values: [Hello, Alice! CAUTION: The queen is angry!]
      Possible solutions: each(groovy.lang.Closure), wait(), every(), any(), dump(), find()
      	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
      	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:54)
      	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
      	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
      	at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:18)
      	at acme.caution(/var/lib/jenkins/jobs/Pipeline-Using-Shared-Library/builds/76/libs/sandbox-shared-library/vars/acme.groovy:12)}}
      

      I think the reason is actually explained on the same website in Section "Accessing steps":

      Library classes cannot directly call steps such as sh or git.

        Attachments

          Issue Links

            Activity

            Hide
            reinholdfuereder Reinhold Füreder added a comment - - edited

            First answering to Liam Newman ("In the example workaround, what does it look like when you call that?"): it is really as I mentioned – this rather ugly workaround "allows to keep the usage as-is"

            acme.name = 'Alice'
            echo acme.name /* prints: 'Alice' */
            acme.caution 'The queen is angry!' /* prints: 'Hello, Alice. CAUTION: The queen is angry!' */
            

            However, I also strongly agree with the perfect suggestion of Jesse Glick on how to adapt the documentation => thanks in advance...

            Show
            reinholdfuereder Reinhold Füreder added a comment - - edited First answering to Liam Newman ("In the example workaround, what does it look like when you call that?"): it is really as I mentioned – this rather ugly workaround "allows to keep the usage as-is" acme.name = 'Alice' echo acme.name /* prints: 'Alice' */ acme.caution 'The queen is angry!' /* prints: 'Hello, Alice. CAUTION: The queen is angry!' */ However, I also strongly agree with the perfect suggestion of Jesse Glick on how to adapt the documentation => thanks in advance...
            Hide
            bitwiseman Liam Newman added a comment - - edited

            Reinhold Füreder
            See PR 611 (linked)

            As a follow on also PR 714. Changed the text to be more directed toward the better way to accomplish things instead of suggesting less than ideal ways.

            Show
            bitwiseman Liam Newman added a comment - - edited Reinhold Füreder See PR 611 (linked) As a follow on also PR 714. Changed the text to be more directed toward the better way to accomplish things instead of suggesting less than ideal ways.
            Hide
            reinholdfuereder Reinhold Füreder added a comment -

            Thanks Liam Newman, I think your changes are certainly a step forward and mostly very nice. Nonetheless I dared to left a few comments in PR 714 though: besides a few minor glitches/typos, there is however still the "Avoid preserving state in global variables" Section that I am still not very happy about it; and one new sentence that I don't get at all, sorry.

            In addition I would find it very helpful to add an example/hint on combining global vars and accessing steps of library classes: Because IMHO this makes the usage code in pipeline script much nicer, as the user does not have to instantiate the library classes including handing over the Pipeline script object via this by herself.

            Please mind that I have not tested this example and I left the com.acme.EmailNotification up to the reader, which is maybe not a good idea? If you think this might be good to include, I will add the com.acme.EmailNotification and also test it...

            vars/acme.groovy
            #!/usr/bin/env groovy
            
            void sendEmailNotification(String subject = 'Build failed', boolean notifyCulprits = true) {
              def emailNotification = new com.acme.EmailNotification(this)
              emailNotification.send(subject, notifyCulprits)
            }
            
            void build(boolean notifyCulprits = true, Closure body) {
              def acmeBuild = new com.acme.Build(this, notifyCulprits)
              acmeBuild.build(body)
            }
            
            vars/acme.groovy
            #!/usr/bin/env groovy
            
            package com.acme
            
            class Build implements Serializable {
            
              def script
              def currentBuild
              boolean notifyCulprits
            
              Build(def script, boolean notifyCulprits) {
                this.script = script
                this.currentBuild = script.currentBuild
                this.notifyCulprits = notifyCulprits
              }
            
              public void build(Closure body) {
                try {
                  body()
                } catch (e) {
                	notifyFailing(Result.FAILURE, e)
            
                	// While there would be no need to re-throw the exception to propagate the error (because the build result must be set
                	// to failure for email-ext anyhow before), re-throw it for e.g. script approval requests:
                	throw e
                }
              }
            
              private void notifyFailing(Result buildResultToSetIfUndefined, def error) {
              	currentBuild.result = currentBuild.result ?: buildResultToSetIfUndefined.toString()
              	script.echo("Build failed: ${error} => Failure notification will be sent...")
            
              	acme.sendEmailNotification('Build failed', notifyCulprits)
              }
            
            }
            
            Show
            reinholdfuereder Reinhold Füreder added a comment - Thanks Liam Newman , I think your changes are certainly a step forward and mostly very nice. Nonetheless I dared to left a few comments in PR 714 though: besides a few minor glitches/typos, there is however still the "Avoid preserving state in global variables" Section that I am still not very happy about it; and one new sentence that I don't get at all, sorry. In addition I would find it very helpful to add an example/hint on combining global vars and accessing steps of library classes : Because IMHO this makes the usage code in pipeline script much nicer, as the user does not have to instantiate the library classes including handing over the Pipeline script object via this by herself. Please mind that I have not tested this example and I left the com.acme.EmailNotification up to the reader, which is maybe not a good idea? If you think this might be good to include, I will add the com.acme.EmailNotification and also test it... vars/acme.groovy #!/usr/bin/env groovy void sendEmailNotification( String subject = 'Build failed' , boolean notifyCulprits = true ) { def emailNotification = new com.acme.EmailNotification( this ) emailNotification.send(subject, notifyCulprits) } void build( boolean notifyCulprits = true , Closure body) { def acmeBuild = new com.acme.Build( this , notifyCulprits) acmeBuild.build(body) } vars/acme.groovy #!/usr/bin/env groovy package com.acme class Build implements Serializable { def script def currentBuild boolean notifyCulprits Build(def script, boolean notifyCulprits) { this .script = script this .currentBuild = script.currentBuild this .notifyCulprits = notifyCulprits } public void build(Closure body) { try { body() } catch (e) { notifyFailing(Result.FAILURE, e) // While there would be no need to re- throw the exception to propagate the error (because the build result must be set // to failure for email-ext anyhow before), re- throw it for e.g. script approval requests: throw e } } private void notifyFailing(Result buildResultToSetIfUndefined, def error) { currentBuild.result = currentBuild.result ?: buildResultToSetIfUndefined.toString() script.echo( "Build failed: ${error} => Failure notification will be sent..." ) acme.sendEmailNotification( 'Build failed' , notifyCulprits) } }
            Hide
            bitwiseman Liam Newman added a comment -

            Reinhold Füreder
            What does your pipeline look like for this? Are you using declarative?

            Show
            bitwiseman Liam Newman added a comment - Reinhold Füreder What does your pipeline look like for this? Are you using declarative?
            Hide
            reinholdfuereder Reinhold Füreder added a comment -

            Liam Newman I think I get your pointer, or in other words that is a very valid question: I am still using scripted Pipeline; and when switching to declarative then the example would have less value for the reader, because e.g. the try-catch around everything can be written much nicer based on the post-build actions or so? If so, then this is certainly not the best example, I must admit...

            Show
            reinholdfuereder Reinhold Füreder added a comment - Liam Newman I think I get your pointer, or in other words that is a very valid question: I am still using scripted Pipeline; and when switching to declarative then the example would have less value for the reader, because e.g. the try-catch around everything can be written much nicer based on the post-build actions or so? If so, then this is certainly not the best example, I must admit...

              People

              • Assignee:
                Unassigned
                Reporter:
                reinholdfuereder Reinhold Füreder
              • Votes:
                2 Vote for this issue
                Watchers:
                5 Start watching this issue

                Dates

                • Created:
                  Updated: