Question
How can I reference this top-level function from within the data class? Or is Java's encapsulation of a class restrictive to the point that you cannot reach beyond the current class?
Code
def String branchName() {
return ((env.GIT_BRANCH ?: 'master') =~ /(?i)^(?:origin\/)?(.*)/)[0][1];
}
public DeployConfig implements IDeployConfig {
public DeployConfig(IDeployConfig config) {
this._appName = config.app;
this._gitUrl = config.gitUrl;
// ... et cetera
}
public String getBranchName() {
return branchName()
}
}
Background
I'm trying to define a data class that represents our standard Jenkinsfile configuration, in an attempt to make our pipeline more testable, and less "cross your fingers and hope it didn't break anything". Toward that goal, here is a snippet of that implementation.
Now, the property getter I'm trying to write doesn't know the actual branch being built when the object is constructed, because that's derived from the Map<String, String>
returned by checkout scm
which gets instantiated at runtime. We assign the GIT_BRANCH
out to the global environment env.GIT_BRANCH
so that it can be referenced elsewhere.
Miscellaneous
To the would-be suggestion of putting the target branch in the Jenkinsfile, that defeats the purpose of the Jenkinsfile being an instruction set for a job with Git configurations assigned, such as a multi-branch job with a shared Jenkinsfile.
Other Code
To give some context about what I mean about the checkout scm
command happening after the construction of DeployConfig
, the pipeline roughly resembles this:
// ./pipeline-library/vars/deploy.groovy
#!/usr/bin/groovy
def call(Closure body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
environmentVariables(config) // assigns certain keys to global env
if (env.IS_PROD) {
deployProd(config)
}
else {
deployNonProd(config)
}
}
// ./pipeline-library/vars/deployNonProd.groovy
#!/usr/bin/groovy
def call(Map config) {
// local variable declarations
pipeline {
agent {
label 'some-configuration-name'
}
environment {
// shared environment variables
}
options {
// configured options, like timestamps and log rotation
}
stages {
stage('Checkout') {
steps {
def gitInfo = checkout scm
env.GIT_BRANCH = gitInfo.GIT_BRANCH
}
}
// additional stages
}
}
}
Edits
Edit: The idea behind the property that calls the top-level function is a computed property that gets called later in the pipeline, after the checkout scm
command has been executed. The DeployConfig
would be constructed before the pipeline runs, and so the branch is not known at that time.
CodePudding user response:
So I solved the problem for myself, but it's arguably a less than ideal solution to the problem. Basically, here's what I had to do:
First, I created a getter getGetBranchName
and setter setGetBranchName
on the class of type Closure<String>
and it had a backing field _getBranchName
. I also created a property getBranchName
of type String
that returned the result of this._getBranchName()
.
Second, if the incoming Map
has a property branchName
, then I set the value this._getBranchName = () -> { return config.branchName }
so that I am referencing the getter of an outer object.
Third, as a final check, I assign the global function signature from Jenkins after constructing the DeployConfig
object. That all looks like the below code (Note: ellipses are used to indicate more code unrelated to the specific solution):
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import com.domain.jenkins.data.DeployConfig
import com.domain.jenkins.data.GitUrl
import com.domain.jenkins.exceptions.InterruptException
import com.domain.jenkins.io.FileSystem
import com.domain.jenkins.pipeline.PipelineJob
import com.domain.jenkins.pipeline.PipelineStage
class Program {
static FileSystem fs
static PipelineJob pipeline
static Map<String, String> env
static Map jenkinsfile
static {
fs = new FileSystem()
pipeline = new PipelineJob()
env = [:]
jenkinsfile = [ ... ]
}
static String branchName() {
return ((env.GIT_BRANCH ?: 'master') =~ /(?i)^(?:origin\/)?(.*)/)[0][1]
}
static void main(String[] args) {
println 'Initialize pipeline'
pipeline = new PipelineJob()
println 'Initialize configuration'
DeployConfig config = new DeployConfig(jenkinsfile)
println new JsonBuilder(config.toMap()).toPrettyString()
println 'Assign static method as getBranchName method'
config.getBranchName = () -> { return branchName() }
println 'Assign environment variables to global env'
env << config.environmentVariables
...
}
}
And the DeployConfig
class accepts that using the following (Note: most of the related code is not included for brevity's sake):
package com.domain.jenkins.data
import com.domain.jenkins.interfaces.IDeployConfig
import com.domain.jenkins.interfaces.IMappable
import com.domain.jenkins.interfaces.Mappable
class DeployConfig extends Mappable implements IDeployConfig, IMappable {
private Closure<String> _getBranchName
DeployConfig() {
this.branchName = 'master'
}
DeployConfig(Map options) {
this.branchName = options.branchName
}
String getBranchName() {
return this._getBranchName()
}
void setBranchName(String value) {
if(this._getBranchName == null) {
this._getBranchName = () -> { return 'master' }
}
if(value) {
this._getBranchName = () -> { return value }
}
}
void setGetBranchName(Closure<String> action) {
this._getBranchName = action
}
}