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

Step jobDsl can be used at most once in pipeline with DELETE

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • job-dsl-plugin
    • None

      The example for the jobDsl pipeline step shows usage that cannot actually be used:

      https://github.com/jenkinsci/job-dsl-plugin/wiki/User-Power-Moves#use-job-dsl-in-pipeline-scripts

      node {
          jobDsl scriptText: 'job("example-2")'
      
          jobDsl targets: ['jobs/projectA/*.groovy', 'jobs/common.groovy'].join('\n'),
                 removedJobAction: 'DELETE',
                 removedViewAction: 'DELETE',
                 lookupStrategy: 'SEED_JOB',
                 additionalClasspath: ['libA.jar', 'libB.jar'].join('\n')
      }
      

      This will always delete the job "example-2" because the second step DELETEs all unfreferenced items, unless of course one of the scripts also happens to create a job named "example-2".

       

      From my brief look at the source, I don't think this would be easy to implement. However, it would still be incredibly useful to be able to call jobDsl multiple times with different arguments.

       

          [JENKINS-44142] Step jobDsl can be used at most once in pipeline with DELETE

          Christian V created issue -

          Christian V added a comment -

          I've taken another look at this.

          When multiple job-dsl scripts are run, job-dsl will attach multiple "actions" to the build. The current code seems to assume that there is only a single one.

           

          To find the last generated objects, it's probably a simple change, maybe something like this:

           

           

          Set<T> findLastGeneratedObjects() {
              for (Run run = job.lastBuild; run != null; run = run.previousBuild) {
                  // We need to fetch all actions because job-dsl could habe been run multiple times in a single build.
          
                  // Previously, it would use the first build, whose first buildAction's modifiedObjects was set
                  // Now, it chooses the first build, where at least one buildAction's modifiedObjects is set
                  List<B> actions = run.getActions(buildActionClass)
                  Set<T> result = []
                  boolean useThisResult = false
                  for (B action : actions) {
                      if (action.modifiedObjects != null) {
                          useThisResult = true
                          result += action.modifiedObjects
                      }
                  }
          
                  if (useThisResult) {
                      return result
                  }
              }
              []
          }
          

          Further, job-dsl needs to be made aware of previous invocations during the same build run.

           

          Currently, it compares the "new items" against the items items from the first suitable previous run. Here's a simple idea:

          GeneratedItems generatedItems = dslScriptLoader.runScripts(scriptRequests);
          mergeWithCurrentRun(generatedItems, run); // <-- this would be the only change
          Set<GeneratedJob> freshJobs = generatedItems.getJobs();
          Set<GeneratedView> freshViews = generatedItems.getViews();
          Set<GeneratedConfigFile> freshConfigFiles = generatedItems.getConfigFiles();
          Set<GeneratedUserContent> freshUserContents = generatedItems.getUserContents();

           

          One could simply pretend that a subsequent invocation also "generated" the items that were generated previously in the same run.

           

          The last invocation's log output would then summarize everything that job-dsl did.

           

          Finally, one could check whether a build action is already appened to the current run and replace it it instead, instead of unconditionally doing run.addAction:

           

          // Save onto Builder, which belongs to a Project.
          run.addAction(new GeneratedJobsBuildAction(freshJobs, getLookupStrategy()));
          run.addAction(new GeneratedViewsBuildAction(freshViews, getLookupStrategy()));
          run.addAction(new GeneratedConfigFilesBuildAction(freshConfigFiles));
          run.addAction(new GeneratedUserContentsBuildAction(freshUserContents));

           

          This might affect JENKINS-29784

           

          daspilker Thoughts?

           

          Christian V added a comment - I've taken another look at this. When multiple job-dsl scripts are run, job-dsl will attach multiple "actions" to the build. The current code seems to assume that there is only a single one.   To find the last generated objects, it's probably a simple change, maybe something like this:     Set<T> findLastGeneratedObjects() { for (Run run = job.lastBuild; run != null ; run = run.previousBuild) { // We need to fetch all actions because job-dsl could habe been run multiple times in a single build. // Previously, it would use the first build, whose first buildAction's modifiedObjects was set // Now, it chooses the first build, where at least one buildAction's modifiedObjects is set List<B> actions = run.getActions(buildActionClass) Set<T> result = [] boolean useThisResult = false for (B action : actions) { if (action.modifiedObjects != null ) { useThisResult = true result += action.modifiedObjects } } if (useThisResult) { return result } } [] } Further, job-dsl needs to be made aware of previous invocations during the same build run.   Currently, it compares the "new items" against the items items from the first suitable previous run. Here's a simple idea: GeneratedItems generatedItems = dslScriptLoader.runScripts(scriptRequests); mergeWithCurrentRun(generatedItems, run); // <-- this would be the only change Set<GeneratedJob> freshJobs = generatedItems.getJobs(); Set<GeneratedView> freshViews = generatedItems.getViews(); Set<GeneratedConfigFile> freshConfigFiles = generatedItems.getConfigFiles(); Set<GeneratedUserContent> freshUserContents = generatedItems.getUserContents();   One could simply pretend that a subsequent invocation also "generated" the items that were generated previously in the same run.   The last invocation's log output would then summarize everything that job-dsl did.   Finally, one could check whether a build action is already appened to the current run and replace it it instead, instead of unconditionally doing run.addAction:   // Save onto Builder, which belongs to a Project. run.addAction( new GeneratedJobsBuildAction(freshJobs, getLookupStrategy())); run.addAction( new GeneratedViewsBuildAction(freshViews, getLookupStrategy())); run.addAction( new GeneratedConfigFilesBuildAction(freshConfigFiles)); run.addAction( new GeneratedUserContentsBuildAction(freshUserContents));   This might affect JENKINS-29784   daspilker Thoughts?  

          Christian V added a comment - - edited

          Hmm, Generated*BuildActions also contains the lookup strategy. One would have to have up to two actions per type, one per possible strategy.

           

          Then, JENKINS-29784 should also be solved, hopefully.

          Christian V added a comment - - edited Hmm, Generated*BuildActions also contains the lookup strategy. One would have to have up to two actions per type, one per possible strategy.   Then, JENKINS-29784 should also be solved, hopefully.
          Daniel Spilker made changes -
          Link New: This issue is duplicated by JENKINS-50875 [ JENKINS-50875 ]

          Markus Baur added a comment -

          This problem took me by surprise and removed history of many jobs. It would be great if it gets addressed soon. It should at least be documented with the DELETE action.

          Markus Baur added a comment - This problem took me by surprise and removed history of many jobs. It would be great if it gets addressed soon. It should at least be documented with the DELETE action.

          Hello,

          it is connected with problem with multiple job dsl action - action with generated objects is added for every job dsl build step, so there is the same number of actions as job-dsl steps. If option DELETE is checked, it compares generated items of current build (if there is unreferenced job/view/config - jobs/views ... which were created by this seed job, but are not included in generated objects by job dsl step). But this comparison contains only  generated items from one action not from the all actions from all job dsl steps because getAction(Class<T> returns only the first hit.  Including them into one action (so do not create action for every job dsl step, but include generated objects into existing action) should fix the problem, but only in case that you use DELETE option only in your last build job dsl step (because generation of all jobs/views/config is not finished if there any job dsl step reminds to execute).

          my pull request is there https://github.com/jenkinsci/job-dsl-plugin/pull/1190, you can check it or comment.

           

           

          Lucie Votypkova added a comment - Hello, it is connected with problem with multiple job dsl action - action with generated objects is added for every job dsl build step, so there is the same number of actions as job-dsl steps. If option DELETE is checked, it compares generated items of current build (if there is unreferenced job/view/config - jobs/views ... which were created by this seed job, but are not included in generated objects by job dsl step). But this comparison contains only  generated items from one action not from the all actions from all job dsl steps because getAction(Class<T> returns only the first hit.  Including them into one action (so do not create action for every job dsl step, but include generated objects into existing action) should fix the problem, but only in case that you use DELETE option only in your last build job dsl step (because generation of all jobs/views/config is not finished if there any job dsl step reminds to execute). my pull request is there https://github.com/jenkinsci/job-dsl-plugin/pull/1190,  you can check it or comment.    
          Lucie Votypkova made changes -
          Link New: This issue is caused by JENKINS-29784 [ JENKINS-29784 ]
          Daniel Spilker made changes -
          Resolution New: Fixed [ 1 ]
          Status Original: Open [ 1 ] New: Fixed but Unreleased [ 10203 ]

          I have used the Job DSL plugin in production for over a year with the DISABLE action in place and I hit a similar issue when using multiple job dsl build steps in a single pipeline. The fix for this issue changed the behavior of the job dsl plugin when using multiple job dsl build steps in parallel. Is that (parallel job dsl build steps) a use case which is interesting to anyone else?

          (The new behavior of the Job DSL plugin ended up disabling many of my jobs. I spent a day with a mostly broken environment because I had to track this down.)

           Based on the discussion here, it seems like the correct thing to do is to use the IGNORE behavior on all uses of the job dsl build step except the very last one, which should not run in parallel with any other job dsl build step. Looking at the available hook points in hudson.model.Run and hudson.model.Job, I think that there isn't a great way to solve this. We could register a new hudson.model.listeners.RunListener that disables or deletes jobs after a successful job completes (and runs Job DSL build steps that DISABLE or DELETE jobs). Has that idea already been considered?

          Daniel Carrington added a comment - I have used the Job DSL plugin in production for over a year with the DISABLE action in place and I hit a similar issue when using multiple job dsl build steps in a single pipeline. The fix for this issue changed the behavior of the job dsl plugin when using multiple job dsl build steps in parallel. Is that (parallel job dsl build steps) a use case which is interesting to anyone else? (The new behavior of the Job DSL plugin ended up disabling many of my jobs. I spent a day with a mostly broken environment because I had to track this down.)  Based on the discussion here, it seems like the correct thing to do is to use the IGNORE behavior on all uses of the job dsl build step except the very last one, which should not run in parallel with any other job dsl build step. Looking at the available hook points in hudson.model.Run and hudson.model.Job, I think that there isn't a great way to solve this. We could register a new hudson.model.listeners.RunListener that disables or deletes jobs after a successful job completes (and runs Job DSL build steps that DISABLE or DELETE jobs). Has that idea already been considered?
          Daniel Spilker made changes -
          Resolution Original: Fixed [ 1 ]
          Status Original: Fixed but Unreleased [ 10203 ] New: Reopened [ 4 ]

            jamietanna Jamie Tanna
            vqrs Christian V
            Votes:
            6 Vote for this issue
            Watchers:
            12 Start watching this issue

              Created:
              Updated: