It sounds to me like you're already aware of this, but there are a couple easy ways to do this, and a couple hard ways to do this.
The easy ways:
- Make your repo available for checkout without SSH keys or authentication credentials.
- Manually add an SSH keypair for repo checkout to the Jenkins user on your Jenkins master node (since Active Choice Groovy scripts run on the master node) (i.e. don't use the Credentials store).
The hard ways:
- Use the Jenkins Java API in your Active Choices code to extract credentials from the credentials store. API docs are available for the credentials store and the ssh credentials type. There is no documentation on how to use the Java classes available in these plugins, so it's up to you to figure it out yourself.
- Create a secondary "meta" job to update the parameters in the original Jenkins job (including Active Choices scripts) - I describe this a bit below.
If you can't do any of the easy solutions for security reasons, there isn't an easy, straightforward way to update Active Choice parameters based on the contents of a git repo. This is because:
- Like you said, "during pre-pipeline stage (i.e. Active Choices parameter) Hudson extensions such as withCredentials or sshAgent are not accessible".
- The configuration for parameters, including Active Choice parameters, is taken from the previous run of that Pipeline, not the current one. Meaning that, to update your parameters from within your Pipeline code, you'll need to run your Pipeline, which defeats the point of what you want to do.
There is a solution for 1., which is to use Scripted Pipelines instead of Declarative, but that won't get around the problems of 2.
The solution that I came up with when I ran into this issue is to create a second Jenkins job (I call it a "meta" job) which updates the parameters of the original job, including Active Choices scripts. This second meta job is triggered by a git hook on the repository that I use to fill the options in my Active Choices parameters. This has the advantage that you do not need to run the original job's Pipeline in order to update its parameters, solving the issues in 2. Also, because it's still a Pipeline job, you can use normal Pipeline steps, thus allowing you to access the credentials store.
My job looks (roughly) like this - I simplified it a lot to make it fit here, so you'll need to do a bunch of tweaking to get it working the way you want. There are probably a ton of errors and bugs in this because I ripped apart an existing script to make this. A few things to note:
- This makes heavy use of Jenkins APIs, so you'll need to either disable the script sandbox or whitelist a ton of functions in the Jenkins security manager in order to get this to run
- This is still technically a Scripted Pipeline even though it looks much more like a normal Groovy script than a Pipeline file
- Because this is a Pipeline, you can use steps like
withCredentials
to check out repositories using the credentials store.
import groovy.json.JsonOutput
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript
import org.biouno.unochoice.*
import org.biouno.unochoice.model.*
def metadataUrl = 'ssh://git@**************/metadata.git'
def metadataBranch = 'master'
def metadata = [:]
node('master') {
git(
url: metadataUrl,
branch: metadataBranch,
credentialsId: 'jenkins',
changelog: false,
)
def dataDir = "${pwd()}/myJsonFiles"
// can't use File.eachFileMatch() due to:
// https://wiki.jenkins.io/display/JENKINS/Pipeline+CPS+method+mismatches
def files = new File(dataDir).list().findAll() { f->
FilenameUtils.getExtension(f) == 'json'
}
files.each() { f->
// TODO: nice error messages if files are misformatted
def name = FilenameUtils.removeExtension(f)
def data = readJson(file: "${dataDir}/${f}")
metadata[name] = data
}
}
// ChoiceParameterDefinition is the class name for the built-in static choice parameter.
// CascadeChoiceParameter is the class name for the Active Choices dynamic choice parameter.
// begin first parameter
def fileParam = new ChoiceParameterDefinition(
'file',
'The JSON file to use',
)
fileParam.setChoices(metadata.keySet())
// end first parameter
// begin second parameter
def metadataJson = JsonOutput.toJson(metadata)
def scriptText = """
import groovy.json.JsonSlurper
def slurper = new JsonSlurper()
def metadata = slurper.parseText('${metadataJson}')
return metadata[file]['versions']
"""
def mainScript = new SecureGroovyScript(scriptText, true)
def fallbackScript = new SecureGroovyScript('return ["Error encountered - see logs"]', true)
def activeChoiceScript = new GroovyScript(mainScript, fallbackScript)
def versionParam = new CascadeChoiceParameter(
'version',
'The version to use',
activeChoiceScript,
'PT_SINGLE_SELECT',
'file',
false,
)
// end second parameter
def paramProp = new ParametersDefinitionProperty(fileParam, versionParam)
// this is the full name of the actual job which needs dynamic parameters
def job = Jenkins.instance.getItemByFullName('UNIX/deploy_to_environment/master')
// There is no "setProperty" - we need to remove and replace.
// removeProperty is safe to run even if no matching properties are set on the job.
// However, this method only removes one property per method call.
// Theoretically, this means the job could accumulate multiple conflicting
// ParametersDefinitionProperty properties.
// TODO: run removeProperty in a while loop to eliminate all matching properties for extra safety.
job.removeProperty(ParametersDefinitionProperty)
job.addProperty(paramProp)