jarondl
jarondl

Reputation: 1663

Jenkins dynamic declarative pipeline parameters

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.

Upvotes: 25

Views: 73072

Answers (5)

VonC
VonC

Reputation: 1329022

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.

Upvotes: 1

pelos
pelos

Reputation: 1876

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

}

Upvotes: 5

Mario R.
Mario R.

Reputation: 366

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.

Upvotes: 19

Eldad Assis
Eldad Assis

Reputation: 11055

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

Upvotes: 15

daggett
daggett

Reputation: 28634

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)
}

Upvotes: 22

Related Questions