21

Can the parameters in a Jenkins declarative pipeline be dynamic?

I want a the choice option values be populated at runtime by a function. The following code does generate a list of options, but they seem to be stale - probably generated on the first time I ran this code. If the list of AMIs changes, the choices remain the same. I want this to run every time I select build with parameters.

def findAMIs() {
    // Find relevant AMIs based on their name
    def sout = new StringBuffer(), serr = new StringBuffer()
    def proc = '/usr/bin/aws --region eu-west-1 ec2 describe-images \
               ' --owners OWNER --filter Name=name,Values=PATTERN \
               ' --query Images[*].{AMI:Name} --output  text'.execute()
    proc.consumeProcessOutput(sout, serr)
    proc.waitForOrKill(10000)
    return sout.tokenize() 
}

def AMIs = findAMIs().join('\n')

pipeline {
    // a declarative pipeline
    agent any

    parameters {
        choice(name: 'Release',
               choices: AMIs)
    }
    ...
 }

EDIT I ended up using jenkins-job-builder, with extended choice parameters. It does not support the groovyScript parameter at the moment, so I modified it https://review.openstack.org/#q,I0c6ac0b49c24b8d3afbc06b003847de2e043c2b8,n,z

EDIT The above link went dead, so here is another link to openstack: https://review.opendev.org/#/c/477003/ But the gist of the matter is I have added a new parameter to jenkins-job-builder called 'groovyScriptFile', which was merged.

jarondl
  • 1,593
  • 4
  • 18
  • 27
  • does it just not change immediately, or does it never change (how many times did you run the build after it should have changed)? i think i've seen it not take effect on the first run. – burnettk Jun 16 '17 at 00:12
  • It never changes. Every time I click build with parameters I get exactly the same options, even though I know the script returns different options (when run manually) – jarondl Jun 16 '17 at 16:54
  • The parameters can be redefined any time a build runs. No build, no redefinition. – Daniel Beck Jun 22 '17 at 11:31
  • 3
    @DanielBeck that's a shame... I would really like to have dynamic choices based on the current situation, and not on the previous build. – jarondl Jun 22 '17 at 18:45
  • 1
    @jarondl There are plugins that implement "dynamic" parameters, like Active Choices, Dynamic Parameter (abandoned IIRC), Extensible Parameter, Extended Parameter. Maybe some of those are pipeline compatible. – Daniel Beck Jun 22 '17 at 18:55
  • Thanks for the pointers @DanielBeck None of these seem to work with Jenkins declarative pipelines, but I'll try – jarondl Jun 23 '17 at 17:07
  • thanks @DanielBeck , see my edit – jarondl Jun 23 '17 at 18:58

5 Answers5

22

what about user input:

def findAMIs() {
    return UUID.randomUUID().toString().split('-').join('\n')
}

node{
    def userInput = input(
        id: 'userInput', message: 'input parameters', parameters: [
            [
                $class: 'ChoiceParameterDefinition',
                name: 'ami',
                choices: findAMIs(),
                description: 'AMI',
            ],
        ]
    )

    echo ("Selected AMI :: "+userInput)
}
daggett
  • 26,404
  • 3
  • 40
  • 56
  • 3
    Hi @daggett, thanks for your help. This is the closest so far, but has two downsides 1. It does not work on declarative pipelines (I'll try to modify it to work). 2. The user flow is much less nice than the build with parameters page. I'll try to get it to work on a declarative pipeline and come back to you – jarondl Jun 22 '17 at 18:47
  • @jarondl - did you get a Declarative syntax example working? If so, please share. – Eldad Assis Jul 19 '17 at 14:26
  • Hi @EldadAK No, unfortunately I couldn't get it to work. Look at the edit to my question - I ended up using JJB – jarondl Jul 20 '17 at 03:44
  • 1
    you can use the code above it in a declarative pipeline by wrapping it in a script, e.g: `pipeline { stages { stage { steps { script { def userInput = input(...)` – Jonas Eicher Nov 09 '20 at 12:50
18

There is another solution: you can use the "properties" step before "pipeline" - there you can use the active choice plugin too:

properties([
    parameters([
        [
            $class: 'ChoiceParameter', 
            choiceType: 'PT_SINGLE_SELECT', 
            description: '', 
            filterable: false, 
            name: 'Release', 
            randomName: 'choice-parameter-21337077649621572', 
            script: [
                $class: 'GroovyScript', 
                fallbackScript: '', 
                script: '''// Find relevant AMIs based on their name
                    def sout = new StringBuffer(), serr = new StringBuffer()
                    def proc = '/usr/bin/aws --region eu-west-1 ec2 describe-images \
                            ' --owners OWNER --filter Name=name,Values=PATTERN \
                            ' --query Images[*].{AMI:Name} --output  text'.execute()
                    proc.consumeProcessOutput(sout, serr)
                    proc.waitForOrKill(10000)
                    return sout.tokenize()'''
            ]
        ]
    ])
])
pipeline {
    ...
}

The only thing is that first time you start your build, it would fail. Second time you start it it should be a "build with parameter".

Hope it helps.

Mario R.
  • 356
  • 1
  • 8
  • 1
    this is not working at all. Getting 'java.lang.IllegalArgumentException: Could not instantiate' error, and it is not running the second time as you wrote. – yorammi Oct 29 '17 at 15:03
  • Just tested on jenkins 2.73.2 (but was initially written for jenkins 2.6x.x) with Active Choice Plugin 1.4 - it works. Did you wrote some pipeline stages and steps? – Mario R. Nov 15 '17 at 09:05
  • I've solved it somehow since I've comment. Switching to other things ;-) – yorammi Nov 15 '17 at 09:17
14

For anyone needing a declarative pipeline syntax option, I found a good solution in another question, which helped me.

This is my suggestion based on it. You should be able to generate a more dynamic list with the code that creates the ${WORKSPACE}/list file

pipeline {
    agent any
    stages {
        stage("Release scope") {
            steps {
                script {
                    // Prepare a list and write to file
                    sh "echo \"patch\nminor\nmajor\" > ${WORKSPACE}/list"

                    // Load the list into a variable
                    env.LIST = readFile (file: "${WORKSPACE}/list")

                    // Show the select input
                    env.RELEASE_SCOPE = input message: 'User input required', ok: 'Release!',
                            parameters: [choice(name: 'RELEASE_SCOPE', choices: env.LIST, description: 'What is the release scope?')]
                }
                echo "Release scope selected: ${env.RELEASE_SCOPE}"
            }
        }
    }
}

I hope this helps

Eldad Assis
  • 10,464
  • 11
  • 52
  • 78
  • Thank you, is there a way to allow multiple choices using checkbox instead of choice? I posted it a a question here: https://stackoverflow.com/q/63981770/2748513 will you be able to help? – rodee Sep 20 '20 at 17:27
4

after i been into the same boat and reading the instructions from https://www.jenkins.io/doc/book/pipeline/jenkinsfile/ 'Handling parameters' section i came with a very easy way since we can run properties before everything else.

put this quick example and modify to your liking, i am creating 2 multiple choose options, one will be hard codded and the other one will use the function at the bottom (outside pipeline node) under the choose options you can use the script{} step.

properties([
            [
            $class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false],
            parameters
            (
                [
                    choice(choices: ['opt1', 'opt2', 'opt3'], description: 'desc', name: 'bla'),
                    choice(choices: script{return_list()}, description: 'some letter', name: 'ble')
                ]
        )
    ]
 )

pipeline {
    agent{
        label "Linux"
    }

    stages{
        stage("frist"){
            steps{
                echo "${params.bla}"
                echo "${params.ble}"
            }
        }
    }
}

def return_list(){
    if ("${JOB_NAME}".contains("bla")){
        env.list_users = "1\n 2\n 3\n"
    }else{
        env.list_users = "a\n b\n c\n"
    }
    return env.list_users
}

you can also put them into a step

stages{
    stage("add more params"){
        steps{
                properties([
                    parameters([
                    choice(choices: [script{return_list()}
                    ],
                    description: 'This is the branch that we will build',
                    name: 'param3')
                    ])
                ])
            
        }
    }

be aware than you have to run it once so the next job will pick up the parameters.(only the latest properties node will appear in the UI)

with this method you can even go to decide if you want to display certain parameter in the UI or not. such

properties([
            [
            $class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false],
            parameters
            (
                script{
                    list_arguments()
                }   
        )
    ]
 )
pipeline {
    agent{
        label "Linux"
    }

    stages{
        stage("frist"){
            steps{
                echo "${params.bla}"
                echo "${params.ble}"
            }
        }
    }
}

def return_list(){
    if ("${JOB_NAME}".contains("bla")){
        env.list_users = "1\n 2\n 3\n"
    }else{
        env.list_users = "a\n b\n c\n"
    }
    return env.list_users
}

def list_arguments(){
    lista = return_list()
    if ("${JOB_NAME}".contains("word")){
        ch = [
            choice(choices: ['opt1', 'opt2', 'opt3'], description: 'desc', name: 'bla'),
            choice(choices: ["${lista}"], description: 'some letter', name: 'ble')
        ]

    }else{
        ch = [
            choice(choices: ['opt1', 'opt2', 'opt3'], description: 'desc', name: 'bla')
        ]
    } 
    return ch

}
pelos
  • 1,744
  • 4
  • 24
  • 34
  • Worked for me without the "$class: 'RebuildSettings' ..." part. In the version of Jenkins I'm using it doesn't seem to have that Groovy class. – whitestryder Mar 01 '23 at 20:27
1

Update 2022: the Jenkins Active Choice plugin (jenkinsci/active-choices-plugin), initially mentioned in Mario R.'s answer in 2017, has evolved.

Its latest 2.60.2 release (June 2022) makes sure to support Jenkins 2.335+, since recent Jenkins releases did modify the DOM, as illustrated with JENKINS-68013.

Current example:

properties([
  parameters([
    [
      $class: 'ChoiceParameter',
      choiceType: 'PT_SINGLE_SELECT',
      name: 'Environment',
      script: [
        $class: 'ScriptlerScript',
        scriptlerScriptId:'Environments.groovy'
      ]
    ],
    [
      $class: 'CascadeChoiceParameter',
      choiceType: 'PT_SINGLE_SELECT',
      name: 'Host',
      referencedParameters: 'Environment',
      script: [
        $class: 'ScriptlerScript',
        scriptlerScriptId:'HostsInEnv.groovy',
        parameters: [
          [name:'Environment', value: '$Environment']
        ]
      ]
   ]
 ])
])

pipeline {
  agent any
  stages {
    stage('Build') {
      steps {
        echo "${params.Environment}"
        echo "${params.Host}"
      }
    }
  }
}

Note JENKINS-63284 and PR 47 will allow for initial Pipeline job support for Groovy properties.
This feature will enable Jenkinsfile, pipelines, cron-scheduled, and jobs triggered via other ways without using the GUI/HTML of Jenkins.

iSWORD
  • 768
  • 15
  • 22
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250