Skip to content
Permalink
Browse files
use automatically generated DSL to create config files
[FIXES JENKINS-39754]
[FIXES JENKINS-40719]
  • Loading branch information
daspilker committed Jan 24, 2017
1 parent 330d96d commit b7fcc87cbe8d25beb9d85d525c136ffba4c6473a
Showing 13 changed files with 228 additions and 37 deletions.
@@ -27,6 +27,11 @@ Browse the Jenkins issue tracker to see any [open issues](https://issues.jenkins

## Release Notes
* 1.58 (unreleased)
* Enhanced support for
[Config File Provider Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Config+File+Provider+Plugin)
([JENKINS-33630](https://issues.jenkins-ci.org/browse/JENKINS-33630),
[JENKINS-39754](https://issues.jenkins-ci.org/browse/JENKINS-39754),
[JENKINS-40719](https://issues.jenkins-ci.org/browse/JENKINS-40719))
* Fixed a problem with the plugin's dependencies
([JENKINS-41001](https://issues.jenkins-ci.org/browse/JENKINS-41001))
* Improved error message for invalid enum values
@@ -124,14 +124,18 @@ When the [Config File Provider Plugin](https://wiki.jenkins-ci.org/display/JENKI
installed, the DSL can be used to create configuration files.

```groovy
configFiles(Closure configFilesClosure = null) // since 1.58
customConfigFile(String name, Closure configFileClosure = null) // since 1.30
mavenSettingsConfigFile(String name, Closure configFileClosure = null) // since 1.30
```

These methods behaves like the [job](#job) methods and will return a config file object.
The `configFiles` method can be used to create any kind of config file that is supported by the
[[Automatically Generated DSL]]. Use the embedded API viewer to browse available methods.

See the [API Viewer](https://jenkinsci.github.io/job-dsl-plugin/) page for details about config file options.
The other methods behaves like the [job](#job) methods and will return a config file object.
See the [API Viewer](https://jenkinsci.github.io/job-dsl-plugin/) page for details about these methods.

Config files will be created before jobs to ensure that the file exists before it is referenced.

@@ -12,30 +12,33 @@ import static groovy.lang.GroovyShell.DEFAULT_CODE_BASE
/**
* Runs provided DSL scripts via an external {@link JobManagement}.
*/
class AbstractDslScriptLoader {
abstract class AbstractDslScriptLoader<S extends JobParent, G extends GeneratedItems> {
private static final Logger LOGGER = Logger.getLogger(AbstractDslScriptLoader.name)
private static final Comparator<? super Item> ITEM_COMPARATOR = new ItemProcessingOrderComparator()

private final JobManagement jobManagement
protected final JobManagement jobManagement
protected final Class<S> scriptBaseClass
protected final Class<G> generatedItemsClass
private final PrintStream logger

/**
* Creates a new {@link AbstractDslScriptLoader} which will use the given {@link JobManagement} instance.
*
* @since 1.45
* @since 1.58
*/
AbstractDslScriptLoader(JobManagement jobManagement) {
protected AbstractDslScriptLoader(JobManagement jobManagement, Class<S> scriptBaseClass,
Class<G> generatedItemsClass) {
this.jobManagement = jobManagement
this.logger = jobManagement.outputStream
this.scriptBaseClass = scriptBaseClass
this.generatedItemsClass = generatedItemsClass
}

/**
* Executes the script requests and returns the generated items.
*
* @since 1.45
*/
GeneratedItems runScripts(Collection<ScriptRequest> scriptRequests) throws IOException {
GeneratedItems generatedItems = new GeneratedItems()
G runScripts(Collection<ScriptRequest> scriptRequests) throws IOException {
G generatedItems = generatedItemsClass.newInstance()
CompilerConfiguration config = createCompilerConfiguration()
Map<String, GroovyShell> groovyShellCache = [:]
try {
@@ -52,20 +55,9 @@ class AbstractDslScriptLoader {
groovyShellCache[key] = groovyShell
}

JobParent jobParent = runScriptEngine(scriptRequest, groovyShell)
S jobParent = runScriptEngine(scriptRequest, groovyShell)

generatedItems.configFiles.addAll(
extractGeneratedConfigFiles(jobParent.referencedConfigFiles, scriptRequest.ignoreExisting)
)
generatedItems.jobs.addAll(
extractGeneratedJobs(jobParent.referencedJobs, scriptRequest.ignoreExisting)
)
generatedItems.views.addAll(
extractGeneratedViews(jobParent.referencedViews, scriptRequest.ignoreExisting)
)
generatedItems.userContents.addAll(
extractGeneratedUserContents(jobParent.referencedUserContents, scriptRequest.ignoreExisting)
)
extractGeneratedItems(generatedItems, jobParent, scriptRequest)

scheduleJobsToRun(jobParent.queueToBuild)
}
@@ -87,11 +79,11 @@ class AbstractDslScriptLoader {
*
* @since 1.47
*/
GeneratedItems runScript(String script) throws IOException {
G runScript(String script) throws IOException {
runScripts([new ScriptRequest(script)])
}

private JobParent runScriptEngine(ScriptRequest scriptRequest, GroovyShell groovyShell) {
protected S runScriptEngine(ScriptRequest scriptRequest, GroovyShell groovyShell) {
try {
if (scriptRequest.scriptPath || scriptRequest.location) {
String scriptName = scriptRequest.location ?: new File(scriptRequest.scriptPath).name
@@ -114,7 +106,7 @@ class AbstractDslScriptLoader {
script.binding = createBinding(scriptRequest)
script.binding.setVariable('jobFactory', script)

JobParent jobParent = (JobParent) script
S jobParent = (S) script
jobParent.setJm(jobManagement)

jobParent.run()
@@ -170,8 +162,26 @@ class AbstractDslScriptLoader {
idx > -1 ? fileName[0..idx - 1] : fileName
}

private Set<GeneratedJob> extractGeneratedJobs(Set<Item> referencedItems,
boolean ignoreExisting) throws IOException {
/**
* @since 1.58
*/
protected void extractGeneratedItems(G generatedItems, S jobParent, ScriptRequest scriptRequest) {
generatedItems.configFiles.addAll(
extractGeneratedConfigFiles(jobParent.referencedConfigFiles, scriptRequest.ignoreExisting)
)
generatedItems.jobs.addAll(
extractGeneratedJobs(jobParent.referencedJobs, scriptRequest.ignoreExisting)
)
generatedItems.views.addAll(
extractGeneratedViews(jobParent.referencedViews, scriptRequest.ignoreExisting)
)
generatedItems.userContents.addAll(
extractGeneratedUserContents(jobParent.referencedUserContents, scriptRequest.ignoreExisting)
)
}

protected Set<GeneratedJob> extractGeneratedJobs(Set<Item> referencedItems,
boolean ignoreExisting) throws IOException {
Set<GeneratedJob> generatedJobs = new LinkedHashSet<GeneratedJob>()
referencedItems.sort(false, ITEM_COMPARATOR).each { Item item ->
if (item instanceof Job) {
@@ -187,7 +197,7 @@ class AbstractDslScriptLoader {
generatedJobs
}

private Set<GeneratedView> extractGeneratedViews(Set<View> referencedViews, boolean ignoreExisting) {
protected Set<GeneratedView> extractGeneratedViews(Set<View> referencedViews, boolean ignoreExisting) {
Set<GeneratedView> generatedViews = new LinkedHashSet<GeneratedView>()
referencedViews.each { View view ->
String xml = view.xml
@@ -198,8 +208,8 @@ class AbstractDslScriptLoader {
generatedViews
}

private Set<GeneratedConfigFile> extractGeneratedConfigFiles(Set<ConfigFile> referencedConfigFiles,
boolean ignoreExisting) {
protected Set<GeneratedConfigFile> extractGeneratedConfigFiles(Set<ConfigFile> referencedConfigFiles,
boolean ignoreExisting) {
Set<GeneratedConfigFile> generatedConfigFiles = new LinkedHashSet<GeneratedConfigFile>()
referencedConfigFiles.each { ConfigFile configFile ->
LOGGER.log(Level.FINE, "Saving config file ${configFile.name}")
@@ -209,8 +219,8 @@ class AbstractDslScriptLoader {
generatedConfigFiles
}

private Set<GeneratedUserContent> extractGeneratedUserContents(Set<UserContent> referencedUserContents,
boolean ignoreExisting) {
protected Set<GeneratedUserContent> extractGeneratedUserContents(Set<UserContent> referencedUserContents,
boolean ignoreExisting) {
Set<GeneratedUserContent> generatedUserContents = new LinkedHashSet<GeneratedUserContent>()
referencedUserContents.each { UserContent userContent ->
LOGGER.log(Level.FINE, "Saving user content ${userContent.path}")
@@ -221,7 +231,7 @@ class AbstractDslScriptLoader {
}

@SuppressWarnings('CatchException')
private void scheduleJobsToRun(List<String> jobNames) {
protected void scheduleJobsToRun(List<String> jobNames) {
Map<String, Throwable> exceptions = [:]
jobNames.each { String jobName ->
try {
@@ -254,7 +264,7 @@ class AbstractDslScriptLoader {

private CompilerConfiguration createCompilerConfiguration() {
CompilerConfiguration config = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
config.scriptBaseClass = 'javaposse.jobdsl.dsl.JobParent'
config.scriptBaseClass = scriptBaseClass.name

// Import some of our helper classes so that user doesn't have to.
ImportCustomizer icz = new ImportCustomizer()
@@ -1,5 +1,6 @@
package javaposse.jobdsl.dsl

import javaposse.jobdsl.dsl.helpers.ConfigFilesContext
import javaposse.jobdsl.dsl.jobs.BuildFlowJob
import javaposse.jobdsl.dsl.jobs.FreeStyleJob
import javaposse.jobdsl.dsl.jobs.IvyJob
@@ -245,6 +246,15 @@ interface DslFactory extends ViewFactory {
@RequiresPlugin(id = 'managed-scripts', minimumVersion = '1.2.1', failIfMissing = true)
ParametrizedConfigFile managedScriptConfigFile(String name, @DslContext(ParametrizedConfigFile) Closure closure)

/**
* Creates managed config files.
*
* @since 1.58
*/
@NoDoc(embeddedOnly = true)
@RequiresPlugin(id = 'config-file-provider')
void configFiles(@DslContext(ConfigFilesContext) Closure closure)

/**
* Upload the stream as <a href="https://wiki.jenkins-ci.org/display/JENKINS/User+Content">user content</a>.
* Use {@link DslFactory#streamFileFromWorkspace(java.lang.String)} to read the content from a file.
@@ -0,0 +1,10 @@
package javaposse.jobdsl.dsl

/**
* Runs provided DSL scripts via an external {@link JobManagement}.
*/
class DslScriptLoader extends AbstractDslScriptLoader<JobParent, GeneratedItems> {
DslScriptLoader(JobManagement jobManagement) {
super(jobManagement, JobParent, GeneratedItems)
}
}
@@ -1,6 +1,7 @@
package javaposse.jobdsl.dsl

import groovy.transform.ThreadInterrupt
import javaposse.jobdsl.dsl.helpers.ConfigFilesContext
import javaposse.jobdsl.dsl.jobs.BuildFlowJob
import javaposse.jobdsl.dsl.jobs.FreeStyleJob
import javaposse.jobdsl.dsl.jobs.IvyJob
@@ -245,6 +246,10 @@ abstract class JobParent extends Script implements DslFactory {
configFile
}

@Override
void configFiles(@DslContext(ConfigFilesContext) Closure closure) {
}

@Override
void userContent(String path, InputStream content) {
referencedUserContents << new UserContent(path, content)
@@ -0,0 +1,8 @@
package javaposse.jobdsl.dsl.helpers

import javaposse.jobdsl.dsl.ContextType
import javaposse.jobdsl.dsl.ExtensibleContext

@ContextType('org.jenkinsci.lib.configprovider.model.Config')
class ConfigFilesContext implements ExtensibleContext {
}
@@ -24,7 +24,6 @@
import hudson.model.ViewGroup;
import hudson.tasks.Builder;
import javaposse.jobdsl.dsl.DslException;
import javaposse.jobdsl.dsl.DslScriptLoader;
import javaposse.jobdsl.dsl.GeneratedConfigFile;
import javaposse.jobdsl.dsl.GeneratedItems;
import javaposse.jobdsl.dsl.GeneratedJob;
@@ -312,7 +311,7 @@ public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnul
getTargets(), isUsingScriptText(), getScriptText(), ignoreExisting, isIgnoreMissingFiles(), additionalClasspath
);

DslScriptLoader dslScriptLoader = new DslScriptLoader(jobManagement);
JenkinsDslScriptLoader dslScriptLoader = new JenkinsDslScriptLoader(jobManagement);
GeneratedItems generatedItems = dslScriptLoader.runScripts(scriptRequests);
Set<GeneratedJob> freshJobs = generatedItems.getJobs();
Set<GeneratedView> freshViews = generatedItems.getViews();
@@ -0,0 +1,36 @@
package javaposse.jobdsl.plugin;

import javaposse.jobdsl.dsl.AbstractDslScriptLoader;
import javaposse.jobdsl.dsl.GeneratedConfigFile;
import javaposse.jobdsl.dsl.GeneratedItems;
import javaposse.jobdsl.dsl.JobManagement;
import javaposse.jobdsl.dsl.ScriptRequest;
import jenkins.model.Jenkins;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.jenkinsci.lib.configprovider.model.Config;
import org.jenkinsci.plugins.configfiles.GlobalConfigFiles;

/**
* @since 1.58
*/
public class JenkinsDslScriptLoader extends AbstractDslScriptLoader<JenkinsJobParent, GeneratedItems> {
public JenkinsDslScriptLoader(JobManagement jobManagement) {
super(jobManagement, JenkinsJobParent.class, GeneratedItems.class);
}

@Override
protected void extractGeneratedItems(GeneratedItems generatedItems, JenkinsJobParent jobParent, ScriptRequest scriptRequest) {
super.extractGeneratedItems(generatedItems, jobParent, scriptRequest);

if (DefaultGroovyMethods.asBoolean(Jenkins.getInstance().getPluginManager().getPlugin("config-file-provider"))) {
GlobalConfigFiles globalConfigFiles = GlobalConfigFiles.get();
for (Object o : jobParent.getReferencedConfigs()) {
Config config = (Config) o;
if (!(scriptRequest.getIgnoreExisting() && globalConfigFiles.getById(config.id) != null)) {
globalConfigFiles.save(config);
}
generatedItems.getConfigFiles().add(new GeneratedConfigFile(config.id, config.name));
}
}
}
}
@@ -0,0 +1,21 @@
package javaposse.jobdsl.plugin

import javaposse.jobdsl.dsl.ContextHelper
import javaposse.jobdsl.dsl.DslContext
import javaposse.jobdsl.dsl.JobParent
import javaposse.jobdsl.dsl.helpers.ConfigFilesContext
import javaposse.jobdsl.plugin.structs.DescribableListContext

abstract class JenkinsJobParent extends JobParent {
private static final String CONFIG_PROVIDER_TYPE = 'org.jenkinsci.lib.configprovider.ConfigProvider'

Set referencedConfigs = new LinkedHashSet<>()

@Override
void configFiles(@DslContext(ConfigFilesContext) Closure closure) {
jm.requirePlugin('config-file-provider', true)
DescribableListContext context = new DescribableListContext(CONFIG_PROVIDER_TYPE, jm)
ContextHelper.executeInContext(closure, context)
referencedConfigs.addAll(context.values)
}
}
@@ -1,9 +1,11 @@
package javaposse.jobdsl.plugin.structs

import hudson.model.Descriptor
import javaposse.jobdsl.dsl.Context
import javaposse.jobdsl.dsl.DslException
import javaposse.jobdsl.dsl.JobManagement
import javaposse.jobdsl.plugin.Messages
import jenkins.model.Jenkins
import org.jenkinsci.plugins.structs.describable.DescribableModel

import static java.lang.String.format
@@ -21,6 +23,16 @@ class DescribableListContext implements Context {
private final JobManagement jobManagement
final List values = []

/**
* @since 1.58
*/
DescribableListContext(String type, JobManagement jobManagement) {
this(
Jenkins.instance.getExtensionList(type).collect { Descriptor d -> new DescribableModel(d.clazz) },
jobManagement
)
}

DescribableListContext(Collection<DescribableModel> types, JobManagement jobManagement) {
this.describableModels = types
this.jobManagement = jobManagement

0 comments on commit b7fcc87

Please sign in to comment.