diff --git a/src/idea.gdsl b/src/idea.gdsl index 3149b06..6af5ffd 100644 --- a/src/idea.gdsl +++ b/src/idea.gdsl @@ -46,6 +46,7 @@ contributor(ctx) { method(name: 'readProperties', type: 'Object', namedParams: [parameter(name: 'defaults', type: 'java.util.Map'), parameter(name: 'file', type: 'java.lang.String'), parameter(name: 'interpolate', type: 'java.lang.Boolean'), parameter(name: 'text', type: 'java.lang.String'),], doc: 'Read properties from files in the workspace or text.') method(name: 'readFile', type: 'java.lang.String', params: [file: 'java.lang.String'], doc: 'Read file from workspace') method(name: 'readFile', type: 'java.lang.String', namedParams: [parameter(name: 'file', type: 'java.lang.String'), parameter(name: 'encoding', type: 'java.lang.String'),], doc: 'Read file from workspace') + method(name: 'fileExists', type: 'Object', params: [file: 'java.lang.String'], doc: 'Verify if file exists in workspace') method(name: 'readTrusted', type: 'Object', params: [path: 'java.lang.String'], doc: 'Read trusted file from SCM') method(name: 'readYaml', type: 'Object', params: [:], doc: 'Read yaml from files in the workspace or text.') method(name: 'readYaml', type: 'Object', namedParams: [parameter(name: 'file', type: 'java.lang.String'), parameter(name: 'text', type: 'java.lang.String'),], doc: 'Read yaml from files in the workspace or text.') diff --git a/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy b/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy index ea09acd..453dc37 100644 --- a/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy +++ b/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy @@ -1,28 +1,46 @@ package ru.pulsar.jenkins.library.configuration +import com.cloudbees.groovy.cps.NonCPS import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.beanutils.BeanUtils import ru.pulsar.jenkins.library.IStepExecutor import ru.pulsar.jenkins.library.ioc.ContextRegistry class ConfigurationReader implements Serializable { - static JobConfiguration create(String config) { + + private static ObjectMapper mapper = new ObjectMapper() + private static final String DEFAULT_CONFIGURATION_RESOURCE = 'globalConfiguration.json' + + static JobConfiguration create() { IStepExecutor steps = ContextRegistry.getContext().getStepExecutor() - def mapper = new ObjectMapper() - def globalConfig = steps.libraryResource 'globalConfiguration.json' + def globalConfig = steps.libraryResource DEFAULT_CONFIGURATION_RESOURCE def globalConfiguration = mapper.readValue(globalConfig, JobConfiguration.class) - def jobConfiguration = mapper.readValue(config, JobConfiguration.class) - - BeanUtils.describe(jobConfiguration).entrySet().stream() - .filter({ e -> e.getValue() != null }) - .filter({ e -> e.getKey() != "class" }) - .filter({ e -> e.getKey() != "metaClass" }) - .forEach { e -> - BeanUtils.setProperty(globalConfiguration, e.getKey(), e.getValue()); - } return globalConfiguration } + + static JobConfiguration create(String config) { + def globalConfiguration = create() + def jobConfiguration = mapper.readValue(config, JobConfiguration.class) + + return mergeConfigurations(globalConfiguration, jobConfiguration); + } + + @NonCPS + private static JobConfiguration mergeConfigurations( + JobConfiguration baseConfiguration, + JobConfiguration configurationToMerge + ) { + BeanUtils.describe(configurationToMerge).entrySet().stream() + .filter({ e -> e.getValue() != null }) + .filter({ e -> e.getKey() != "class" }) + .filter({ e -> e.getKey() != "metaClass" }) + .forEach {e -> + BeanUtils.setProperty(baseConfiguration, e.getKey(), e.getValue()); + } + + return baseConfiguration; + } } diff --git a/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy b/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy index aad3ee8..6697df6 100644 --- a/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy +++ b/src/ru/pulsar/jenkins/library/configuration/JobConfiguration.groovy @@ -2,6 +2,8 @@ package ru.pulsar.jenkins.library.configuration import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonPropertyDescription +import org.apache.commons.lang3.builder.ReflectionToStringBuilder +import org.apache.commons.lang3.builder.ToStringStyle @JsonIgnoreProperties(ignoreUnknown = true) class JobConfiguration implements Serializable { @@ -10,4 +12,9 @@ class JobConfiguration implements Serializable { @JsonPropertyDescription("Имя настроенной утилиты sonar-scanner.") String sonarScannerToolName + + @Override + String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.NO_CLASS_NAME_STYLE) + } } \ No newline at end of file diff --git a/test/integration/groovy/jobConfigurationTest.groovy b/test/integration/groovy/jobConfigurationTest.groovy new file mode 100644 index 0000000..df32047 --- /dev/null +++ b/test/integration/groovy/jobConfigurationTest.groovy @@ -0,0 +1,77 @@ +import org.apache.commons.io.IOUtils +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition +import org.jenkinsci.plugins.workflow.job.WorkflowJob +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.jvnet.hudson.test.JenkinsRule + +import java.nio.charset.StandardCharsets + +class jobConfigurationTest { + + @Rule + public JenkinsRule rule = new JenkinsRule() + + @Before + void configureGlobalGitLibraries() { + RuleBootstrapper.setup(rule) + } + + @Test + void "jobConfiguration should not fail without file"() { + + def pipeline = """ + pipeline { + agent any + stages { + stage('test') { + steps { + echo jobConfiguration().toString() + } + } + } + } + """.stripIndent() + final CpsFlowDefinition flow = new CpsFlowDefinition(pipeline, true) + final WorkflowJob workflowJob = rule.createProject(WorkflowJob, 'project') + workflowJob.definition = flow + + rule.buildAndAssertSuccess(workflowJob) + } + + @Test + void "jobConfiguration should merge configurations"() { + + def file = IOUtils.resourceToString( + 'jobConfiguration.json', + StandardCharsets.UTF_8, + this.getClass().getClassLoader() + ); + + def writeFile = """ + writeFile text: \"\"\"$file\"\"\", file: 'jobConfiguration.json' + """ + + def pipeline = """ + pipeline { + agent any + stages { + stage('test') { + steps { + $writeFile + echo jobConfiguration().toString() + } + } + } + } + """.stripIndent() + final CpsFlowDefinition flow = new CpsFlowDefinition(pipeline, true) + final WorkflowJob workflowJob = rule.createProject(WorkflowJob, 'project') + workflowJob.definition = flow + + def run = rule.buildAndAssertSuccess(workflowJob) + rule.assertLogContains('v8version=8.3.12.1500', run) + rule.assertLogContains('sonarScannerToolName=sonar-scanner', run) + } +} \ No newline at end of file diff --git a/test/integration/resources/jobConfiguration.json b/test/integration/resources/jobConfiguration.json new file mode 100644 index 0000000..b776078 --- /dev/null +++ b/test/integration/resources/jobConfiguration.json @@ -0,0 +1,3 @@ +{ + "v8version": "8.3.12.1500" +} \ No newline at end of file diff --git a/vars/jobConfiguration.groovy b/vars/jobConfiguration.groovy index eacb5cf..96215f7 100644 --- a/vars/jobConfiguration.groovy +++ b/vars/jobConfiguration.groovy @@ -2,9 +2,14 @@ import ru.pulsar.jenkins.library.configuration.ConfigurationReader import ru.pulsar.jenkins.library.configuration.JobConfiguration import ru.pulsar.jenkins.library.ioc.ContextRegistry -JobConfiguration call(String path = "") { +JobConfiguration call(String path = "jobConfiguration.json") { ContextRegistry.registerDefaultContext(this) - def config = readFile(path) - return ConfigurationReader.create(config) + if (fileExists(path)) { + def config = readFile(path) + return ConfigurationReader.create(config) + } else { + return ConfigurationReader.create() + } + } \ No newline at end of file