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

Provide more object oriented 'load'-step esque functionality

    • Icon: New Feature New Feature
    • Resolution: Duplicate
    • Icon: Minor Minor
    • pipeline
    • None
    • Jenkins core 2.16, latest pipeline plugins

      It seems right now that there are 3 ways to share pipeline code:

      1. Writing a plugin
      2. Creating a global "master" library using the Pipeline Global Library plugin
      3. load ing a Groovy script

      As a pipeline writer, I'd like to be able to load or "import" additional classes that allow me to modularize my Jenkinsfile into separate packages and classes that I have control and ownership of.

      Current issues

      Based on my experience, these are some of the issues we have ran into:

      With load step

      • load ed scripts cannot contain class , enum, or other types
      • every script must return this, which can be easy to forget and sometimes hard to debug, especially newcomers
      • difficult to test
      Attempting to create a "DSL" with load ed script

      One way I tried, was to have a load ed script have a method that would return a Map<String, Closure> as a workaround. Something along these lines:

      Jenkinsfile

      #!groovy
      
      properties([
        [
          $class: 'BuildDiscarderProperty',
          strategy: [
            '$class':
              'LogRotator', numToKeepStr: '20'
          ]
        ]
      ])
      
      node {
        checkout scm
        final myScript = load 'myScript.groovy'
      
        final mike = myScript.withName('Mike')
      
        mike.sayHi()
        mike.sayHello()
      }
      

      myScript.groovy

      Map<String, Closure> withName(String name) {
        Closure sayHi = { ->
          say('Hi', name)
        }
        Closure sayHello = { ->
          say('Hello', name)
        }
      
        return [
          sayHi: sayHi,
          sayHello: sayHello,
        ]
      }
      
      void say(String phrase, String name) {
        echo "$phrase, $name"
      }
      
      return this
      

      When this is attempted to run, I kind of expected a script whitelist issue, but this exception was a little different than I expected:

      org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: unclassified method java.util.LinkedHashMap sayHi
      	at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:113)
      	at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:149)
      	at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:146)
      	at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:15)
      	at WorkflowScript.run(WorkflowScript:19)
      	at ___cps.transform___(Native Method)
      	at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:55)
      	at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:106)
      	at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:74)
      	at sun.reflect.GeneratedMethodAccessor712.invoke(Unknown Source)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
      	at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
      	at com.cloudbees.groovy.cps.Next.step(Next.java:58)
      	at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
      	at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
      	at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
      	at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:29)
      	at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
      	at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:29)
      	at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:164)
      	at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:360)
      	at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:80)
      	at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:236)
      	at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:226)
      	at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:47)
      	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      	at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
      	at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
      	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
      	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      	at java.lang.Thread.run(Thread.java:745)
      

      With Global CPS

      • requires committing and pushing a git repository to Jenkins
      • can only have 1 "set" of global libraries which lends itself to the "Jenkins master"s being the controllers of the globally shared libraries
      • consumers can't control the global dependency "version". must be version pushed to Jenkins
      • difficult to test

      Proposal

      Provide an import-type step allows pipeline authors to write classes in a similar manner to the Pipeline Global Library plugin but can be easily loaded on the classpath.

          [JENKINS-37270] Provide more object oriented 'load'-step esque functionality

          Mike Kobit created issue -
          Mike Kobit made changes -
          Description Original: It seems right now that there are 3 ways to share pipeline code:

          # Writing a plugin
          # Creating a global "master" library using the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin]
          # {{load}}ing a Groovy script

          As a pipeline writer, I'd like to be able to {{load}} or "{{import}}" additional classes that allow me to modularize my {{Jenkinsfile}} into separate packages and classes that I have control and ownership of.

          h3. Current issues

          Based on my experience, these are some of the issues we have ran into:

          h4. With {{load}} step

          * {{load}}ed scripts cannot contain {{class}}, {{enum}}, or other types
          * every script must {{return this}}, which can be easy to forget and sometimes hard to debug, especially newcomers
          * difficult to test

          h5. Attempting to create a "DSL" with {{load}}ed script

          One way I tried, was to have a {{load}}ed script have a method that would return a {{Map<String, Closure>}} as a workaround. Something along these lines:

          *Jenkinsfile*
          {code:none}
          #!groovy

          properties([
            [
              $class: 'BuildDiscarderProperty',
              strategy: [
                '$class':
                  'LogRotator', numToKeepStr: '20'
              ]
            ]
          ])

          node {
            checkout scm
            final myScript = load 'myScript.groovy'

            final mike = myScript.withName('Mike')

            mike.sayHi()
            mike.sayHello()
          }
          {code}

          *myScript.groovy*

          {code:none}
          Map<String, Closure> withName(String name) {
            Closure sayHi = { ->
              say('Hi', name)
            }
            Closure sayHello = { ->
              say('Hello', name)
            }

            return [
              sayHi: sayHi,
              sayHello: sayHello,
            ]
          }

          void say(String phrase, String name) {
            echo "$phrase, $name"
          }

          return this
          {code}

          When this is attempted to run, I kind of expected a script whitelist issue, but this exception was a little different than I expected:

          {noformat}
          org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: unclassified method java.util.LinkedHashMap sayHi
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:113)
          at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:149)
          at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:146)
          at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:15)
          at WorkflowScript.run(WorkflowScript:19)
          at ___cps.transform___(Native Method)
          at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:55)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:106)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:74)
          at sun.reflect.GeneratedMethodAccessor712.invoke(Unknown Source)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
          at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
          at com.cloudbees.groovy.cps.Next.step(Next.java:58)
          at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:164)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:360)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:80)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:236)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:226)
          at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:47)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
          at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
          at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
          at java.lang.Thread.run(Thread.java:745)
          {noformat}


          h4. With Global CPS

          * requires committing and pushing a git repository to Jenkins
          * can only have 1 "set" of global libraries which lends itself to the "Jenkins master"s being the controllers of the globally shared libraries
          * consumers can't control the global dependency "version". must be version pushed to Jenkins
          * difficult to test

          h4. Proposal

          Provide an {{import}}-type step allows pipeline authors to write classes in a similar manner to the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin] but can be easily loaded on the classpath.
          New: It seems right now that there are 3 ways to share pipeline code:

          # Writing a plugin
          # Creating a global "master" library using the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin]
          # {{load}} ing a Groovy script

          As a pipeline writer, I'd like to be able to {{load}} or "{{import}}" additional classes that allow me to modularize my {{Jenkinsfile}} into separate packages and classes that I have control and ownership of.

          h3. Current issues

          Based on my experience, these are some of the issues we have ran into:

          h4. With {{load}} step

          * {{load}}ed scripts cannot contain {{class}}, {{enum}}, or other types
          * every script must {{return this}}, which can be easy to forget and sometimes hard to debug, especially newcomers
          * difficult to test

          h5. Attempting to create a "DSL" with {{load}}ed script

          One way I tried, was to have a {{load}}ed script have a method that would return a {{Map<String, Closure>}} as a workaround. Something along these lines:

          *Jenkinsfile*
          {code:none}
          #!groovy

          properties([
            [
              $class: 'BuildDiscarderProperty',
              strategy: [
                '$class':
                  'LogRotator', numToKeepStr: '20'
              ]
            ]
          ])

          node {
            checkout scm
            final myScript = load 'myScript.groovy'

            final mike = myScript.withName('Mike')

            mike.sayHi()
            mike.sayHello()
          }
          {code}

          *myScript.groovy*

          {code:none}
          Map<String, Closure> withName(String name) {
            Closure sayHi = { ->
              say('Hi', name)
            }
            Closure sayHello = { ->
              say('Hello', name)
            }

            return [
              sayHi: sayHi,
              sayHello: sayHello,
            ]
          }

          void say(String phrase, String name) {
            echo "$phrase, $name"
          }

          return this
          {code}

          When this is attempted to run, I kind of expected a script whitelist issue, but this exception was a little different than I expected:

          {noformat}
          org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: unclassified method java.util.LinkedHashMap sayHi
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:113)
          at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:149)
          at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:146)
          at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:15)
          at WorkflowScript.run(WorkflowScript:19)
          at ___cps.transform___(Native Method)
          at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:55)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:106)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:74)
          at sun.reflect.GeneratedMethodAccessor712.invoke(Unknown Source)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
          at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
          at com.cloudbees.groovy.cps.Next.step(Next.java:58)
          at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:164)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:360)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:80)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:236)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:226)
          at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:47)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
          at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
          at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
          at java.lang.Thread.run(Thread.java:745)
          {noformat}


          h4. With Global CPS

          * requires committing and pushing a git repository to Jenkins
          * can only have 1 "set" of global libraries which lends itself to the "Jenkins master"s being the controllers of the globally shared libraries
          * consumers can't control the global dependency "version". must be version pushed to Jenkins
          * difficult to test

          h4. Proposal

          Provide an {{import}}-type step allows pipeline authors to write classes in a similar manner to the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin] but can be easily loaded on the classpath.
          Mike Kobit made changes -
          Description Original: It seems right now that there are 3 ways to share pipeline code:

          # Writing a plugin
          # Creating a global "master" library using the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin]
          # {{load}} ing a Groovy script

          As a pipeline writer, I'd like to be able to {{load}} or "{{import}}" additional classes that allow me to modularize my {{Jenkinsfile}} into separate packages and classes that I have control and ownership of.

          h3. Current issues

          Based on my experience, these are some of the issues we have ran into:

          h4. With {{load}} step

          * {{load}}ed scripts cannot contain {{class}}, {{enum}}, or other types
          * every script must {{return this}}, which can be easy to forget and sometimes hard to debug, especially newcomers
          * difficult to test

          h5. Attempting to create a "DSL" with {{load}}ed script

          One way I tried, was to have a {{load}}ed script have a method that would return a {{Map<String, Closure>}} as a workaround. Something along these lines:

          *Jenkinsfile*
          {code:none}
          #!groovy

          properties([
            [
              $class: 'BuildDiscarderProperty',
              strategy: [
                '$class':
                  'LogRotator', numToKeepStr: '20'
              ]
            ]
          ])

          node {
            checkout scm
            final myScript = load 'myScript.groovy'

            final mike = myScript.withName('Mike')

            mike.sayHi()
            mike.sayHello()
          }
          {code}

          *myScript.groovy*

          {code:none}
          Map<String, Closure> withName(String name) {
            Closure sayHi = { ->
              say('Hi', name)
            }
            Closure sayHello = { ->
              say('Hello', name)
            }

            return [
              sayHi: sayHi,
              sayHello: sayHello,
            ]
          }

          void say(String phrase, String name) {
            echo "$phrase, $name"
          }

          return this
          {code}

          When this is attempted to run, I kind of expected a script whitelist issue, but this exception was a little different than I expected:

          {noformat}
          org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: unclassified method java.util.LinkedHashMap sayHi
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:113)
          at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:149)
          at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:146)
          at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:15)
          at WorkflowScript.run(WorkflowScript:19)
          at ___cps.transform___(Native Method)
          at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:55)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:106)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:74)
          at sun.reflect.GeneratedMethodAccessor712.invoke(Unknown Source)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
          at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
          at com.cloudbees.groovy.cps.Next.step(Next.java:58)
          at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:164)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:360)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:80)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:236)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:226)
          at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:47)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
          at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
          at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
          at java.lang.Thread.run(Thread.java:745)
          {noformat}


          h4. With Global CPS

          * requires committing and pushing a git repository to Jenkins
          * can only have 1 "set" of global libraries which lends itself to the "Jenkins master"s being the controllers of the globally shared libraries
          * consumers can't control the global dependency "version". must be version pushed to Jenkins
          * difficult to test

          h4. Proposal

          Provide an {{import}}-type step allows pipeline authors to write classes in a similar manner to the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin] but can be easily loaded on the classpath.
          New: It seems right now that there are 3 ways to share pipeline code:

          # Writing a plugin
          # Creating a global "master" library using the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin]
          # {{load}} ing a Groovy script

          As a pipeline writer, I'd like to be able to {{load}} or "{{import}}" additional classes that allow me to modularize my {{Jenkinsfile}} into separate packages and classes that I have control and ownership of.

          h3. Current issues

          Based on my experience, these are some of the issues we have ran into:

          h4. With {{load}} step

          * {{load}} ed scripts cannot contain {{class}} , {{enum}}, or other types
          * every script must {{return this}}, which can be easy to forget and sometimes hard to debug, especially newcomers
          * difficult to test

          h5. Attempting to create a "DSL" with {{load}} ed script

          One way I tried, was to have a {{load}} ed script have a method that would return a {{Map<String, Closure>}} as a workaround. Something along these lines:

          *Jenkinsfile*
          {code:none}
          #!groovy

          properties([
            [
              $class: 'BuildDiscarderProperty',
              strategy: [
                '$class':
                  'LogRotator', numToKeepStr: '20'
              ]
            ]
          ])

          node {
            checkout scm
            final myScript = load 'myScript.groovy'

            final mike = myScript.withName('Mike')

            mike.sayHi()
            mike.sayHello()
          }
          {code}

          *myScript.groovy*

          {code:none}
          Map<String, Closure> withName(String name) {
            Closure sayHi = { ->
              say('Hi', name)
            }
            Closure sayHello = { ->
              say('Hello', name)
            }

            return [
              sayHi: sayHi,
              sayHello: sayHello,
            ]
          }

          void say(String phrase, String name) {
            echo "$phrase, $name"
          }

          return this
          {code}

          When this is attempted to run, I kind of expected a script whitelist issue, but this exception was a little different than I expected:

          {noformat}
          org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: unclassified method java.util.LinkedHashMap sayHi
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:113)
          at org.kohsuke.groovy.sandbox.impl.Checker$1.call(Checker.java:149)
          at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:146)
          at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:15)
          at WorkflowScript.run(WorkflowScript:19)
          at ___cps.transform___(Native Method)
          at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:55)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:106)
          at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:74)
          at sun.reflect.GeneratedMethodAccessor712.invoke(Unknown Source)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
          at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
          at com.cloudbees.groovy.cps.Next.step(Next.java:58)
          at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:154)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
          at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:29)
          at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:164)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:360)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:80)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:236)
          at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:226)
          at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:47)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
          at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
          at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
          at java.util.concurrent.FutureTask.run(FutureTask.java:266)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
          at java.lang.Thread.run(Thread.java:745)
          {noformat}


          h4. With Global CPS

          * requires committing and pushing a git repository to Jenkins
          * can only have 1 "set" of global libraries which lends itself to the "Jenkins master"s being the controllers of the globally shared libraries
          * consumers can't control the global dependency "version". must be version pushed to Jenkins
          * difficult to test

          h4. Proposal

          Provide an {{import}}-type step allows pipeline authors to write classes in a similar manner to the [Pipeline Global Library plugin|https://github.com/jenkinsci/workflow-cps-global-lib-plugin] but can be easily loaded on the classpath.
          Andrew Bayer made changes -
          Component/s New: pipeline-general [ 21692 ]
          Andrew Bayer made changes -
          Component/s Original: workflow-plugin [ 18820 ]
          Jesse Glick made changes -
          Link New: This issue duplicates JENKINS-31155 [ JENKINS-31155 ]
          Jesse Glick made changes -
          Resolution New: Duplicate [ 3 ]
          Status Original: Open [ 1 ] New: Resolved [ 5 ]

            jglick Jesse Glick
            mkobit Mike Kobit
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: