Skip to content

Commit

Permalink
Support configuration as code (#262)
Browse files Browse the repository at this point in the history
* Add CasC support for the PrioritySorterConfiguration class

The PrioritySorterConfiguration class couldn't be configured with CasC
(Configuration as Code) jenkins plugin.

* Add support for configuration as code plugin usage

The configuration for the priority sorter plugin can be defined with
the [configuration as code plugin](https://github.com/jenkinsci/configuration-as-code-plugin).

This is the example from the junit test resource file `PriorityConfiguration.yaml`
```
unclassified:
  priorityConfiguration:
    jobGroups:
      - id: 0
        priority: 1
        description: "Complex"
        runExclusive: true
        usePriorityStrategies: true
        priorityStrategies:
          - userIdCauseStrategy:
              priority: 1
          - upstreamCauseStrategy
          - userIdCauseStrategy:
              priority: 3
          - cLICauseStrategy:
              priority: 4
          - jobPropertyStrategy
          - buildParameterStrategy:
              parameterName: priority
        jobGroupStrategy:
          folderBased:
            folderName: "Jenkins"
      - id: 1
        priority: 2
        description: "Simple"
        runExclusive: false
        usePriorityStrategies: false
        jobGroupStrategy: allJobs
```

* Add test assertion for the casc priorityStrategies configuration

Extend the ConfigurationAsCodeTest test case to also assert the casc
defined priorityStrategies configurations.

The test casc configuration resource files for the ConfigurationAsCodeTest
test case class are at a similar location then all the other configuration
test files.

* Exclude commons-lang3 from test dependency

* Format current files

* Fix merge error

* Format source files

* Exclude one new spotbugs warning

* Use DataBoundConstructor in JobGroup

* Fix errors when holder contains array

* Resolve errors when exporting configuration using casc

* Extract exportTest to separate class

* Include new icon from master branch

* Add README section about casc.

* Use hamcrest assertThat, not deprecated JUnit assertThat

* Add a round trip JCasC test with JCasC test class

* Test contents of job groups

* Add another JCasC round trip test

---------

Co-authored-by: Frank Ittermann <[email protected]>
Co-authored-by: Mark Waite <[email protected]>
Co-authored-by: kmykitiuk <[email protected]>
  • Loading branch information
4 people authored Jun 10, 2023
1 parent 1a5c0d9 commit e90616b
Show file tree
Hide file tree
Showing 23 changed files with 979 additions and 108 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,55 @@ The column will show the priority used the last time the job was
launched, and, if the job has not been started yet, the column will show
*Pending*.

## Configuration as Code support

Priority Sorter Plugin has support for use of [Configuration as Code plugin](https://plugins.jenkins.io/configuration-as-code/).

To provide priority configuration follow below example:
```yaml
unclassified:
priorityConfiguration:
jobGroups:
- id: 0
priority: 1
description: "Complex"
runExclusive: true
usePriorityStrategies: true
priorityStrategies:
- priorityStrategy:
id: 0
priorityStrategy:
userIdCauseStrategy:
priority: 1
- priorityStrategy:
id: 1
priorityStrategy:
upstreamCauseStrategy
jobGroupStrategy:
folderBased:
folderName: "Jenkins"
- id: 1
priority: 2
description: "Simple"
runExclusive: false
usePriorityStrategies: false
jobGroupStrategy: allJobs
```
Global settings for priority sorter can be configured like this:
```yaml
unclassified:
prioritySorterConfiguration:
onlyAdminsMayEditPriorityConfiguration: true
strategy:
absoluteStrategy:
defaultPriority: 3
numberOfPriorities: 5
```
For more examples see [tests resources](https://github.com/jenkinsci/priority-sorter-plugin/tree/master/src/test/resources/jenkins/advancedqueue/test/ConfigurationAsCodeTest).
## Notable changes and upgrading
### Upgrading from 2.x
Expand Down
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@
<artifactId>workflow-durable-task-step</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jenkins</groupId>
<artifactId>configuration-as-code</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- JCasC compatibility -->
<dependency>
<groupId>io.jenkins.configuration-as-code</groupId>
<artifactId>test-harness</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
Expand Down
120 changes: 60 additions & 60 deletions src/main/java/jenkins/advancedqueue/JobGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import hudson.model.Queue.Item;
import java.util.ArrayList;
import java.util.List;
import jenkins.advancedqueue.jobinclusion.JobInclusionStrategy;
import jenkins.advancedqueue.jobinclusion.strategy.ViewBasedJobInclusionStrategy;
import jenkins.advancedqueue.priority.PriorityStrategy;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundSetter;

/**
* Describes job group for Advanced Queue Sorter.
Expand All @@ -44,10 +47,14 @@
*/
public class JobGroup {

public static class PriorityStrategyHolder {
public static class PriorityStrategyHolder extends PriorityStrategy {
private int id = 0;
private PriorityStrategy priorityStrategy;

@Extension
@Symbol("priorityStrategy")
public static class PriorityStrategyHolderDescriptor extends Descriptor<PriorityStrategy> {}

public PriorityStrategyHolder() {}

@DataBoundConstructor
Expand All @@ -71,6 +78,26 @@ public PriorityStrategy getPriorityStrategy() {
public void setPriorityStrategy(PriorityStrategy priorityStrategy) {
this.priorityStrategy = priorityStrategy;
}

@Override
public Descriptor<PriorityStrategy> getDescriptor() {
return Jenkins.get().getDescriptor(this.getClass());
}

@Override
public boolean isApplicable(Item item) {
return priorityStrategy.isApplicable(item);
}

@Override
public int getPriority(Item item) {
return priorityStrategy.getPriority(item);
}

@Override
public void numberPrioritiesUpdates(int oldNumberOfPriorities, int newNumberOfPriorities) {
priorityStrategy.numberPrioritiesUpdates(oldNumberOfPriorities, newNumberOfPriorities);
}
}

private int id = 0;
Expand All @@ -97,9 +124,10 @@ public void setPriorityStrategy(PriorityStrategy priorityStrategy) {
private String jobPattern = ".*";

private boolean usePriorityStrategies;
private List<JobGroup.PriorityStrategyHolder> priorityStrategies = new ArrayList<JobGroup.PriorityStrategyHolder>();
private List<PriorityStrategyHolder> priorityStrategies = new ArrayList<>();

private JobGroup() {}
@DataBoundConstructor
public JobGroup() {}

/**
* @return the id
Expand All @@ -111,6 +139,7 @@ public int getId() {
/**
* @param id the id to set
*/
@DataBoundSetter
public void setId(int id) {
this.id = id;
}
Expand All @@ -119,6 +148,7 @@ public void setId(int id) {
return hudson.Util.fixNull(description);
}

@DataBoundSetter
public void setDescription(String description) {
this.description = description;
}
Expand All @@ -132,7 +162,6 @@ public int getPriority() {

/**
* @return the viewName or <code>null</code> if the strategy is not {@link jenkins.advancedqueue.jobinclusion.strategy.ViewBasedJobInclusionStrategy}
*
* @deprecated Used in 2.x now replaced with dynamic {@link JobGroup#jobGroupStrategy}, will return the view
*/
@Deprecated
Expand All @@ -147,6 +176,7 @@ public String getView() {
/**
* @param priority the priority to set
*/
@DataBoundSetter
public void setPriority(int priority) {
this.priority = priority;
}
Expand All @@ -155,11 +185,16 @@ public JobInclusionStrategy getJobGroupStrategy() {
// Convert from 2.x
if (jobGroupStrategy == null && view != null) {
ViewBasedJobInclusionStrategy.JobPattern pattern = new ViewBasedJobInclusionStrategy.JobPattern(jobPattern);
jobGroupStrategy = new ViewBasedJobInclusionStrategy(view, useJobFilter == false ? null : pattern);
ViewBasedJobInclusionStrategy viewBasedJobInclusionStrategy = new ViewBasedJobInclusionStrategy(view);
if (useJobFilter) {
viewBasedJobInclusionStrategy.setJobFilter(pattern);
}
jobGroupStrategy = viewBasedJobInclusionStrategy;
}
return jobGroupStrategy;
}

@DataBoundSetter
public void setJobGroupStrategy(JobInclusionStrategy jobGroupStrategy) {
this.view = null;
this.jobGroupStrategy = jobGroupStrategy;
Expand All @@ -169,6 +204,7 @@ public boolean isRunExclusive() {
return runExclusive;
}

@DataBoundSetter
public void setRunExclusive(boolean runExclusive) {
this.runExclusive = runExclusive;
}
Expand All @@ -177,6 +213,7 @@ public boolean isUsePriorityStrategies() {
return usePriorityStrategies;
}

@DataBoundSetter
public void setUsePriorityStrategies(boolean usePriorityStrategies) {
this.usePriorityStrategies = usePriorityStrategies;
}
Expand All @@ -185,60 +222,23 @@ public List<JobGroup.PriorityStrategyHolder> getPriorityStrategies() {
return priorityStrategies;
}

public void setPriorityStrategies(List<JobGroup.PriorityStrategyHolder> priorityStrategies) {
this.priorityStrategies = priorityStrategies;
}

/**
* Creates a Job Group from JSON object.
*
* @param jobGroupObject JSON object with class description
* @param id ID of the item to be created
* @return created group
*/
// TODO: replace by DataBound Constructor
public static JobGroup newInstance(StaplerRequest req, JSONObject jobGroupObject, int id) {
JobGroup jobGroup = new JobGroup();
jobGroup.setId(id);
jobGroup.setDescription(jobGroupObject.getString("description"));
jobGroup.setPriority(jobGroupObject.getInt("priority"));
JSONObject jsonObjectJobGroupStrategy = jobGroupObject.getJSONObject("jobGroupStrategy");
JobInclusionStrategy jobGroupStrategy =
req.bindJSON(Class.class, JobInclusionStrategy.class, jsonObjectJobGroupStrategy);
jobGroup.setJobGroupStrategy(jobGroupStrategy);
jobGroup.setRunExclusive(Boolean.parseBoolean(jobGroupObject.getString("runExclusive")));
/*
jobGroup.setUseJobFilter(jobGroupObject.has("useJobFilter"));
if (jobGroup.isUseJobFilter()) {
JSONObject jsonObject = jobGroupObject.getJSONObject("useJobFilter");
jobGroup.setJobPattern(jsonObject.getString("jobPattern"));
// Disable the filter if the pattern is invalid
try {
Pattern.compile(jobGroup.getJobPattern());
} catch (PatternSyntaxException e) {
jobGroup.setUseJobFilter(false);
@DataBoundSetter
public void setPriorityStrategies(List<? extends PriorityStrategy> priorityStrategies) {
if (priorityStrategies != null && priorityStrategies.size() > 0) {
if (priorityStrategies.get(0) instanceof PriorityStrategyHolder) {
this.priorityStrategies = (List<PriorityStrategyHolder>) priorityStrategies;
} else {
this.priorityStrategies = convertToPriorityStrategyHolder((List<PriorityStrategy>) priorityStrategies);
}
}
*/
//
jobGroup.setUsePriorityStrategies(jobGroupObject.has("usePriorityStrategies"));
if (jobGroup.isUsePriorityStrategies()) {
JSONObject jsonObject = jobGroupObject.getJSONObject("usePriorityStrategies");
if (jsonObject.has("holder")) {
JSONArray jsonArray = JSONArray.fromObject(jsonObject.get("holder"));
int psid = 0;
for (Object object : jsonArray) {
PriorityStrategyHolder holder = new JobGroup.PriorityStrategyHolder();
holder.setId(psid++);
PriorityStrategy strategy = req.bindJSON(Class.class, PriorityStrategy.class, object);
holder.setPriorityStrategy(strategy);
jobGroup.priorityStrategies.add(holder);
}
}
if (jobGroup.priorityStrategies.isEmpty()) {
jobGroup.setUsePriorityStrategies(false);
}
}

private List<JobGroup.PriorityStrategyHolder> convertToPriorityStrategyHolder(
List<PriorityStrategy> priorityStrategies) {
List<JobGroup.PriorityStrategyHolder> priorityHolderStrategies = new ArrayList<>(priorityStrategies.size());
for (int i = 0; i < priorityStrategies.size(); i++) {
priorityHolderStrategies.add(new JobGroup.PriorityStrategyHolder(i, priorityStrategies.get(i)));
}
return jobGroup;
return priorityHolderStrategies;
}
}
36 changes: 26 additions & 10 deletions src/main/java/jenkins/advancedqueue/PriorityConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import hudson.ExtensionList;
import hudson.Plugin;
import hudson.matrix.MatrixConfiguration;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.Queue;
Expand All @@ -54,6 +53,7 @@
import javax.servlet.ServletException;
import jenkins.advancedqueue.jobinclusion.JobInclusionStrategy;
import jenkins.advancedqueue.priority.PriorityStrategy;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
Expand All @@ -70,8 +70,7 @@
* @since 2.0
*/
@Extension
public class PriorityConfiguration extends Descriptor<PriorityConfiguration>
implements RootAction, IconSpec, Describable<PriorityConfiguration> {
public class PriorityConfiguration extends GlobalConfiguration implements RootAction, IconSpec {

private static final Logger LOGGER = Logger.getLogger(PriorityConfiguration.class.getName());

Expand All @@ -82,7 +81,7 @@ public class PriorityConfiguration extends Descriptor<PriorityConfiguration>
private List<JobGroup> jobGroups;

public PriorityConfiguration() {
super(PriorityConfiguration.class);
super();
jobGroups = new LinkedList<JobGroup>();
load();
//
Expand Down Expand Up @@ -143,6 +142,11 @@ public List<JobGroup> getJobGroups() {
return jobGroups;
}

public void setJobGroups(List<JobGroup> jobGroups) {
this.jobGroups = jobGroups;
save();
}

public JobGroup getJobGroup(int id) {
return id2jobGroup.get(id);
}
Expand Down Expand Up @@ -174,18 +178,17 @@ public void doPriorityConfigSubmit(StaplerRequest req, StaplerResponse rsp) thro
if (jobGroupObject.isEmpty()) {
break;
}
JobGroup jobGroup = JobGroup.newInstance(req, jobGroupObject, id++);
jobGroupObject.element("id", id++);
transformPriorityStrategiesData(jobGroupObject);

JobGroup jobGroup = req.bindJSON(JobGroup.class, jobGroupObject);
jobGroups.add(jobGroup);
id2jobGroup.put(jobGroup.getId(), jobGroup);
}
save();
rsp.sendRedirect(Jenkins.get().getRootUrl());
}

public Descriptor<PriorityConfiguration> getDescriptor() {
return this;
}

public FormValidation doCheckJobPattern(@QueryParameter String value) throws IOException, ServletException {
if (value.length() > 0) {
try {
Expand All @@ -206,6 +209,19 @@ public PriorityConfigurationCallback getPriority(Queue.Item item, PriorityConfig
}
}

private void transformPriorityStrategiesData(JSONObject jobGroupObject) {
if (jobGroupObject.has("usePriorityStrategies")) {
JSONObject usePriorityStrategies = jobGroupObject.getJSONObject("usePriorityStrategies");
if (usePriorityStrategies.has("holder")) {
JSONArray priorityStrategies = JSONArray.fromObject(usePriorityStrategies.get("holder"));
jobGroupObject.element("priorityStrategies", priorityStrategies);
jobGroupObject.element("usePriorityStrategies", true);
} else {
jobGroupObject.element("usePriorityStrategies", false);
}
}
}

private PriorityConfigurationCallback getPriorityInternal(
Queue.Item item, PriorityConfigurationCallback priorityCallback) {
if (placeholderTaskHelper.isPlaceholderTask(item.task)) {
Expand Down Expand Up @@ -308,6 +324,6 @@ private PriorityConfigurationCallback getPriorityForJobGroup(
}

public static PriorityConfiguration get() {
return (PriorityConfiguration) Jenkins.get().getDescriptor(PriorityConfiguration.class);
return GlobalConfiguration.all().get(PriorityConfiguration.class);
}
}
Loading

0 comments on commit e90616b

Please sign in to comment.