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

load step fails to bind "complex" @Field defined variables

    XMLWordPrintable

Details

    Description

      for more complex variables that are annotated with @Field, the load step fails to bind the variable into the Script class.

      For example:

      // a.groovy
      import groovy.transform.Field
      
      @Field
      def LOADED_BUILD_NUMBER = ${env.BUILD_NUMBER}
      
      return this
      
      // Jenkinsfile
      node() {
        def a = load('a.groovy')
        echo(${env.BUILD_NUMBER})
        echo(${a.LOADED_BUILD_NUMBER})
      }
      

      This example will fail. However, if you replace ${env.BUILD_NUMBER} with a simple type such as `3`, the load step will succeed.

       

      This seems to be related to the security update in workflow-cps v2.64 and the subsequent regression fix for @Field in v2.71.

      Attachments

        Activity

          llibicpep Dee Kryvenko added a comment -

          Surprisingly, `binding` property is not the same inside and outside of a step, although `evaluate` method explicitly passing in the parent `binding`. So my attempt to use `binding` to communicate between my abstraction layers are failing too.

          llibicpep Dee Kryvenko added a comment - Surprisingly, `binding` property is not the same inside and outside of a step, although `evaluate` method explicitly passing in the parent `binding`. So my attempt to use `binding` to communicate between my abstraction layers are failing too.
          llibicpep Dee Kryvenko added a comment -

          I probably should not be surprised, but that didn't work too. Obviously it works for a very simple case, but if `output.dsl` actually calls any libs steps, which in turn are trying to access `context` - they have no access.

          Obviously, it would work if the `context` were to be used as an argument inside the `output.dsl` every time it calls any library step, but at that point it defeats a purpose of the library, and all I get is a spaghetti code with methods (steps) that needs 50 arguments. Not to mention that I would need to refactor a library in a backward incompatible way, changing all the steps signatures.

          llibicpep Dee Kryvenko added a comment - I probably should not be surprised, but that didn't work too. Obviously it works for a very simple case, but if `output.dsl` actually calls any libs steps, which in turn are trying to access `context` - they have no access. Obviously, it would work if the `context` were to be used as an argument inside the `output.dsl` every time it calls any library step, but at that point it defeats a purpose of the library, and all I get is a spaghetti code with methods (steps) that needs 50 arguments. Not to mention that I would need to refactor a library in a backward incompatible way, changing all the steps signatures.
          jglick Jesse Glick added a comment -

          IIUC just use

          evaluate(output.dsl)(context)
          

          where the output of the DSL is something like

          { context ->
            // as before
          }
          
          jglick Jesse Glick added a comment - IIUC just use evaluate(output.dsl)(context) where the output of the DSL is something like { context -> // as before }
          llibicpep Dee Kryvenko added a comment -

          duemir with all the time I already wasted on fighting artificial issues and limitations - sorry, no. I am looking for ways to get off of Jenkins at this point asap - there's so many great open source kubernetes native alternatives out there today like Tekton that are not flawed and are not making their goal to artificially limit and cripple user experience so that a commercial offering really shines comparing to open source.

          llibicpep Dee Kryvenko added a comment - duemir with all the time I already wasted on fighting artificial issues and limitations - sorry, no. I am looking for ways to get off of Jenkins at this point asap - there's so many great open source kubernetes native alternatives out there today like Tekton that are not flawed and are not making their goal to artificially limit and cripple user experience so that a commercial offering really shines comparing to open source.
          llibicpep Dee Kryvenko added a comment - - edited

          Project factory:

          library 'some-loader-lib@v1'
          loaderStep(this)
          

          Loader:

          def call(def jenkinsfile) {
              def context = Context.it() // data in singletone
              def output
              podTemplate(yaml: """
          apiVersion: v1
          kind: Pod
          metadata:
          spec:
            containers:
              - name: generator
                image: ...
                command:
                  - sleep
                args:
                  - 5m
          """) {
                  node(POD_LABEL) {
                      container('generator') {
                          writeJSON(file: 'input.json', pretty: 4, json: context.input)
                          output = readJSON(returnPojo: true, text: sh(returnStdout: true, script: 'cat input.json | generator').trim())
                      }
                  }
              }
              context.save(output.templates) // there is a whole bunch of heavy string resources like scripts and config files
              context.save(output.etc) // and some other stuff that might not be as heavy
              library output.lib // a library with collection of custom steps generator telling the loaded to load
              evaluate(output.dsl) // now this string evaluating there will call steps from the above and these steps needs access to the context, including templates and etc
          }
          

          Does it makes sense now?

          llibicpep Dee Kryvenko added a comment - - edited Project factory: library 'some-loader-lib@v1' loaderStep( this ) Loader: def call(def jenkinsfile) { def context = Context.it() // data in singletone def output podTemplate(yaml: """ apiVersion: v1 kind: Pod metadata: spec: containers: - name: generator image: ... command: - sleep args: - 5m """) { node(POD_LABEL) { container( 'generator' ) { writeJSON(file: 'input.json' , pretty: 4, json: context.input) output = readJSON(returnPojo: true , text: sh(returnStdout: true , script: 'cat input.json | generator' ).trim()) } } } context.save(output.templates) // there is a whole bunch of heavy string resources like scripts and config files context.save(output.etc) // and some other stuff that might not be as heavy library output.lib // a library with collection of custom steps generator telling the loaded to load evaluate(output.dsl) // now this string evaluating there will call steps from the above and these steps needs access to the context, including templates and etc } Does it makes sense now?

          People

            Unassigned Unassigned
            duemir Denys Digtiar
            Votes:
            1 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated: