Skip to content

Commit

Permalink
Support an inline box to specify dsl scripts, close #21
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Ryan committed May 22, 2012
1 parent 37b9352 commit cfd2dad
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 37 deletions.
7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -82,11 +82,11 @@ To build:

To run Jenkins and test JPI:

./gradlew server
./gradlew server

Build job-dsl.hpi to be installed in Jenkins:

./gradlew jpi
./gradlew jpi

Usage
-----
Expand All @@ -101,9 +101,10 @@ and enter your job script locations (relative to this job's "workspace") as a ne
5. Finally, it is good practice to organise your Jenkins UI with some new tabs so that the management and template
jobs are not the first thing a user sees when they login

Author
Authors
------
Justin Ryan <jryan@netflix.com>

Andrew Harmel-Law <andrew@harmel-law.com>

Artifacts
Expand Down
25 changes: 20 additions & 5 deletions build.gradle
Expand Up @@ -13,7 +13,7 @@ apply plugin: 'jpi'
repositories {
maven {
name 'jenkin-ci'
url "http://maven.jenkins-ci.org/content/repositories/releases"
url 'http://maven.jenkins-ci.org/content/repositories/releases'
}
mavenCentral()
}
Expand All @@ -23,9 +23,14 @@ configurations.testCompile.exclude group: 'org.jenkins-ci.modules', module:'ssh-

dependencies {
//groovy localGroovy() // Can't guarantee 1.8.6
groovy "org.codehaus.groovy:groovy-all:1.8.6"
testCompile "org.spockframework:spock-core:0.6-groovy-1.8"
testCompile "xmlunit:xmlunit:1.1"
groovy 'org.codehaus.groovy:groovy-all:1.8.6'
testCompile 'org.spockframework:spock-core:0.6-groovy-1.8'
testCompile 'xmlunit:xmlunit:1.1'
testCompile 'junit:junit:4.10'
}

test {
useJUnit() // Causes "failed to create temp file to extract class from jar into"
}

group = "org.jenkinsci.plugins"
Expand All @@ -52,5 +57,15 @@ jenkinsPlugin {
}
}

task createWrapper(type: Wrapper) { gradleVersion = '1.0-rc-1' }
task createWrapper(type: Wrapper) { gradleVersion = '1.0-rc-3' }

task(showTestClasspath) << {
println "Classpath:"
println "${configurations.testCompile.resolvedConfiguration}"
println "${configurations.compile.allArtifacts}"
configurations.testCompile.files.each {
println it
}
println "${configurations.testCompile.allDependencies}"
println "${configurations.jenkinsTest.allArtifacts}"
}
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Apr 12 10:05:08 PDT 2012
#Wed May 16 10:59:17 PDT 2012
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.0-rc-1-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.0-rc-3-bin.zip
108 changes: 89 additions & 19 deletions src/main/groovy/javaposse/jobdsl/plugin/ExecuteDslScripts.java
@@ -1,5 +1,6 @@
package javaposse.jobdsl.plugin;

import com.google.common.collect.Lists;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
Expand All @@ -13,6 +14,7 @@
import hudson.tasks.Builder;

import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -21,35 +23,72 @@
import javaposse.jobdsl.dsl.GeneratedJob;
import jenkins.model.Jenkins;

import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;

import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.kohsuke.stapler.StaplerRequest;

/**
* This Builder keeps a list of job DSL scripts, and when prompted, executes these to create /
* update Jenkins jobs.
*
*
* @author jryan
*/
public class ExecuteDslScripts extends Builder {
private static final Logger LOGGER = Logger.getLogger(ExecuteDslScripts.class.getName());

// Artifact of how Jelly/Stapler puts conditional variables in blocks, which NEED to map to a sub-Object.
// The alternative would have been to mess with DescriptorImpl.getInstance
public static class ScriptLocation {
@DataBoundConstructor
public ScriptLocation(String value, String targets, String scriptText) {
this.usingScriptText = value == null || Boolean.parseBoolean(value);
this.targets = Util.fixEmptyAndTrim(targets);
this.scriptText = Util.fixEmptyAndTrim(scriptText);
}

private final boolean usingScriptText;
private final String targets;
private final String scriptText;

}

/**
* Newline-separated list of locations to dsl scripts
*/
private final String targets;
private final String scriptText;
private final boolean usingScriptText;

@DataBoundConstructor
public ExecuteDslScripts(String targets) {
this.targets = Util.fixEmptyAndTrim(targets);
public ExecuteDslScripts(ScriptLocation scriptLocation) {
// Copy over from embedded object
this.usingScriptText = scriptLocation == null || scriptLocation.usingScriptText;
this.targets = scriptLocation==null?null:scriptLocation.targets; // May be null;
this.scriptText = scriptLocation==null?null:scriptLocation.scriptText; // May be null
}

ExecuteDslScripts(String scriptText) {
this.usingScriptText = true;
this.scriptText = scriptText;
this.targets = null;
}

public String getTargets() {
return targets;
}

public String getScriptText() {
return scriptText;
}

public boolean isUsingScriptText() {
return usingScriptText;
}

// Track what jobs got created/updated, we don't want to depend on the builds
Set<GeneratedJob> generatedJobs;

Expand All @@ -65,7 +104,7 @@ public Action getProjectAction(AbstractProject<?, ?> project) {
/**
* Runs every job DSL script provided in the plugin configuration, which results in new /
* updated Jenkins jobs. The created / updated jobs are reported in the build result.
*
*
* @param build
* @param launcher
* @param listener
Expand All @@ -80,27 +119,36 @@ public boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher,
EnvVars env = build.getEnvironment(listener);
env.overrideAll(build.getBuildVariables());

String targetsStr = env.expand(this.targets);
LOGGER.log(Level.FINE, String.format("Expanded targets to %s", targetsStr));
String[] targets = targetsStr.split("\n");
List<String> bodies = Lists.newArrayList();
if (usingScriptText) {
LOGGER.log(Level.INFO, "Using dsl from string");
bodies.add(scriptText);
} else {
String targetsStr = env.expand(this.targets);
LOGGER.log(Level.FINE, String.format("Expanded targets to %s", targetsStr));
String[] targets = targetsStr.split("\n");

for (String target : targets) {
FilePath targetPath = build.getModuleRoot().child(target);
if (!targetPath.exists()) {
targetPath = build.getWorkspace().child(target);
if (!targetPath.exists()) {
listener.fatalError("Unable to find DSL script at " + target);
return false;
}
}
LOGGER.log(Level.INFO, String.format("Running dsl from %s", targetPath));

String dslBody = targetPath.readToString();
bodies.add(dslBody);
}
}
// We run the DSL, it'll need some way of grabbing a template config.xml and how to save it
// They'll make REST calls, we'll make internal Jenkins calls
JenkinsJobManagement jm = new JenkinsJobManagement();

Set<GeneratedJob> modifiedJobs = Sets.newHashSet();
for (String target : targets) {
FilePath targetPath = build.getModuleRoot().child(target);
if (!targetPath.exists()) {
targetPath = build.getWorkspace().child(target);
if (!targetPath.exists()) {
listener.fatalError("Unable to find DSL script at " + target);
return false;
}
}
LOGGER.log(Level.INFO, String.format("Running dsl from %s", targetPath));

String dslBody = targetPath.readToString();
for (String dslBody: bodies) {
LOGGER.log(Level.FINE, String.format("DSL Content: %s", dslBody));

// Room for one dsl to succeed and another to fail, yet jobs from the first will finish
Expand All @@ -114,6 +162,8 @@ public boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher,
generatedJobs = Sets.newHashSet();
}

// TODO Pull all this out, so that it can run outside of the plugin, e.g. JenkinsRestApiJobManagement

// Update Project
Set<GeneratedJob> removedJobs = Sets.difference(generatedJobs, modifiedJobs);
// TODO Print to listener, so that it shows up in the build
Expand Down Expand Up @@ -168,6 +218,26 @@ public static final class DescriptorImpl extends Descriptor<Builder> {
public String getDisplayName() {
return "Process Job DSLs";
}

static final String defaultDsl = "job {\n" +
" using 'TMPL-test'\n" +
" name 'PROJ-integ-tests'\n" +
" configure { node ->\n" +
" configureScm(node)\n" +
" triggers.'hudson.triggers.TimerTrigger'.spec = '15 1,13 * * *'\n" +
" goals = '-e clean integTest'\n" +
" }\n" +
"}\n";

@Override
public Builder newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return super.newInstance(req, formData);
}

@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
return super.configure(req, json);
}
}

}
4 changes: 2 additions & 2 deletions src/main/groovy/javaposse/jobdsl/plugin/SeedJobsProperty.java
Expand Up @@ -7,6 +7,7 @@
import hudson.model.Descriptor;
import hudson.model.JobProperty;
import hudson.model.AbstractProject;
import hudson.model.JobPropertyDescriptor;
import hudson.tasks.Builder;

import com.google.common.collect.Sets;
Expand All @@ -31,9 +32,8 @@ public Collection<String> getSeedJobs() {
return seedJobs;
}


@Extension
public static final class DescriptorImpl extends Descriptor<Builder> {
public static final class DescriptorImpl extends JobPropertyDescriptor {
public String getDisplayName() {
return "Seed Jobs Being Referenced";
}
Expand Down
@@ -1,9 +1,15 @@
package javaposse.jobdsl.plugin.ExecuteDslScripts;

import lib.LayoutTagLib
def f=namespace(lib.FormTagLib)

f=namespace(lib.FormTagLib)

f.entry(title:_("DSL Scripts"),field:"targets") {
f.expandableTextbox()
f.radioBlock(name: 'scriptLocation', value: 'true', title: 'Use the provided DSL script', checked: instance.usingScriptText) {
f.entry(title: 'DSL Script', field: 'scriptText') {
// TODO CodeMirror support for text/x-groovy. It was unclear how do it from a .groovy stapler script
f.textarea(style: 'width:100%; height:10em')
}
}
f.radioBlock(name: 'scriptLocation', value: 'false', title: 'Look on Filesystem', checked: !instance.usingScriptText) {
f.entry(title: 'DSL Scripts', field: 'targets') {
f.expandableTextbox()
}
}
@@ -0,0 +1,3 @@
<div>
Job DSL Script, which is groovy code. Look at <a href="https://github.com/JavaPosseRoundup/job-dsl-plugin">documentation</a> for details on the syntax.
</div>
@@ -1,3 +1,3 @@
<div>
Newline separated list of DSL scripts.
Newline separated list of DSL scripts, located in the Workspace.
</div>
46 changes: 46 additions & 0 deletions src/test/groovy/javaposse/jobdsl/plugin/SeedJobTest.groovy
@@ -0,0 +1,46 @@
package javaposse.jobdsl.plugin;

import org.jvnet.hudson.test.JenkinsRule;
import org.apache.commons.io.FileUtils;
import hudson.model.*;
import hudson.tasks.Shell;
import org.junit.Rule;
import org.junit.Test;

public class SeedJobTest { // extends HudsonTestCase {
@Rule
public JenkinsRule j = new JenkinsRule();

private static final String templateProjectName = "TMPL";

@Test
public void createTemplateTest() throws Exception {
// java.lang.NoClassDefFoundError: hudson/tasks/Ant$AntInstallation
// FreeStyleProject project = j.createFreeStyleProject()
// project.getBuildersList().add(new Shell("echo hello"))
// project.setDisplayName(templateProjectName)
}

// @Test
// public void createSeedJob() throws Exception {
// String templateProjectName = createTemplateTest();
//
// String dsl = '''
//job {
// using 'TMPL'
// name 'unit-test'
//}
//'''
// FreeStyleProject project = j.createFreeStyleProject();
// project.getBuildersList().add(new ExecuteDslScripts(dsl));
//
// FreeStyleBuild build = project.scheduleBuild2(0).get();
// System.out.println(build.getDisplayName()+" completed");
//
// String s = FileUtils.readFileToString(build.getLogFile());
// assertThat(s, contains("+ echo hello"));
//
// // TODO Check for new "unit-test" job
// Project createdProj = (Project) j.jenkins.getItem('unit-test')
// }
}

0 comments on commit cfd2dad

Please sign in to comment.