diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54a9cde --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +target + +# mvn hpi:run +work + +# IntelliJ IDEA project files +*.iml +*.iws +*.ipr +.idea + +# Eclipse project files +.settings +.classpath +.project diff --git a/README.md b/README.md new file mode 100644 index 0000000..18d3b83 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Jenkins JCasC Queue Job Plugin + +This Jenkins plugin enables queueing jobs from the [JCasC](https://www.jenkins.io/projects/jcasc/) config using [Job DSL](https://plugins.jenkins.io/job-dsl/). +Job DSL on its own can already queue jobs, but since JCasC runs before the Jenkins Queue is initialized any jobs queued during this initialization phase are discarded. +This plugin defers jobs queued by Job DSL during initialization until after Jenkins has fully loaded. + +### Usage + +To queue a job via JCasC simply use the following example snippet: +```yaml +jobs: + - script: | + queue("my_job") +``` + +### Development + +Starting a development Jenkins instance with this plugin: `mvn hpi:run` + +Building the plugin: `mvn package` diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1c619e8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + org.jenkins-ci.plugins + plugin + 4.41 + + + + swiss.dasch.plugins + jcasc-queue-job + ${revision} + JCasC Queue Job Plugin + + hpi + + + 1.0 + + 2.332.4 + + 8 + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + + + + io.jenkins.tools.bom + bom-2.332.x + 1451.v15f1fdb_772a_f + pom + import + + + + + + + org.jenkins-ci.plugins + structs + + + org.jenkins-ci.plugins + job-dsl + 1.79 + + + io.jenkins + configuration-as-code + test + + + + diff --git a/src/main/java/swiss/dasch/plugins/jcascqueuejob/StartupItemListener.java b/src/main/java/swiss/dasch/plugins/jcascqueuejob/StartupItemListener.java new file mode 100644 index 0000000..923426f --- /dev/null +++ b/src/main/java/swiss/dasch/plugins/jcascqueuejob/StartupItemListener.java @@ -0,0 +1,34 @@ +package swiss.dasch.plugins.jcascqueuejob; + +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import hudson.Extension; +import hudson.model.BuildableItem; +import hudson.model.Cause; +import hudson.model.Item; +import hudson.model.Queue.QueueDecisionHandler; +import hudson.model.listeners.ItemListener; + +@Extension +public class StartupItemListener extends ItemListener { + + @Override + public void onLoaded() { + StartupQueueDecisionHandler instance = QueueDecisionHandler.all().get(StartupQueueDecisionHandler.class); + + if (instance != null) { + LinkedHashMap items = instance.getAndClearQueuedItems(); + + for (Entry entry : items.entrySet()) { + BuildableItem item = entry.getKey(); + Cause cause = entry.getValue(); + + item.checkPermission(Item.BUILD); + + item.scheduleBuild(cause); + } + } + } + +} diff --git a/src/main/java/swiss/dasch/plugins/jcascqueuejob/StartupQueueDecisionHandler.java b/src/main/java/swiss/dasch/plugins/jcascqueuejob/StartupQueueDecisionHandler.java new file mode 100644 index 0000000..243e964 --- /dev/null +++ b/src/main/java/swiss/dasch/plugins/jcascqueuejob/StartupQueueDecisionHandler.java @@ -0,0 +1,86 @@ +package swiss.dasch.plugins.jcascqueuejob; + +import java.util.LinkedHashMap; +import java.util.List; + +import javax.annotation.Nullable; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import hudson.Extension; +import hudson.init.InitMilestone; +import hudson.init.Initializer; +import hudson.model.Action; +import hudson.model.BuildableItem; +import hudson.model.Cause; +import hudson.model.CauseAction; +import hudson.model.Queue.QueueDecisionHandler; +import hudson.model.Queue.Task; +import hudson.security.ACL; +import jenkins.model.Jenkins; + +@Extension +public class StartupQueueDecisionHandler extends QueueDecisionHandler { + + private static final String JOB_DSL_CAUSE_CLASS = "javaposse.jobdsl.plugin.JenkinsJobManagement$JobDslCause"; + + private static boolean isLoading; + + @Restricted(NoExternalUse.class) + @Initializer(before = InitMilestone.SYSTEM_CONFIG_LOADED) + public static void onBeforeSystemConfigLoaded() { + isLoading = true; + } + + @Restricted(NoExternalUse.class) + @Initializer(after = InitMilestone.JOB_CONFIG_ADAPTED) + public static void onAfterJobConfigAdapted() { + isLoading = false; + } + + private LinkedHashMap queuedItems = new LinkedHashMap<>(); + + public LinkedHashMap getAndClearQueuedItems() { + LinkedHashMap items; + + synchronized (this) { + items = this.queuedItems; + this.queuedItems = new LinkedHashMap<>(); + } + + return items; + } + + @Override + public boolean shouldSchedule(Task task, List actions) { + if (isLoading && Jenkins.getAuthentication2() == ACL.SYSTEM2 && task instanceof BuildableItem) { + Cause cause = getJobDSLCause(actions); + if (cause != null) { + // Task was queued by JobDSL during initialization. Cancel scheduling for + // now and schedule it again later once Jenkins Queue has finished loading. + synchronized (this) { + this.queuedItems.put((BuildableItem) task, cause); + } + return false; + } + } + return true; + } + + @Nullable + private Cause getJobDSLCause(List actions) { + for (Action action : actions) { + if (action instanceof CauseAction) { + CauseAction causeAction = (CauseAction) action; + for (Cause cause : causeAction.getCauses()) { + if (JOB_DSL_CAUSE_CLASS.equals(cause.getClass().getName())) { + return cause; + } + } + } + } + return null; + } + +} diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly new file mode 100644 index 0000000..bb9f9e2 --- /dev/null +++ b/src/main/resources/index.jelly @@ -0,0 +1,4 @@ + +
+ JCasC Queue Job Plugin +