Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 106 additions & 1 deletion docs_src/docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,109 @@ Because of the waterfall: number->match->regex(es), in addition with switches, a
* `I` - search is case insensitive
* Unlike original old `itemId` mode, item target is used also in `exact` match mode, and not only in regex mode.

Strict api is currently not used, is considered as experimental, but shoudl resolve any issue the versatile api may failedue to its number->match->regex(es) waterfall nature.
Strict api is currently not used, is considered as experimental, but shoudl resolve any issue the versatile api may failedue to its number->match->regex(es) waterfall nature.

## Print Queue API

**Since 1.4.13**, a new API endpoint has been added to retrieve the current queue state as a plaintext list.

### Overview

The Print Queue API allows you to retrieve the current build queue as a simple plaintext list, with one job display name per line. This is useful for monitoring, scripting, or integration with external tools.

### Endpoints

#### Safe API (with CSRF protection)
```
POST http://${JENKINS_URL}/simpleMove/printQueue
```
- Requires POST method with CSRF token (crumb)
- Requires `SIMPLE_QUEUE_MOVE_PERMISSION`

#### Unsafe API (without CSRF protection)
```
GET/POST http://${JENKINS_URL}/simpleMoveUnsafe/printQueue
```
- Works with both GET and POST methods
- No CSRF token required
- Must be explicitly enabled in plugin configuration (`enableUnsafe`)

### Parameters

- **`buildable`** (optional): Controls which items to include in the output
- `true` or omitted (default): Shows only buildable items
- `false`: Shows all items in the queue (including blocked/waiting items)
- **`viewName`** (optional): Filter queue items by view
- If specified, only shows items visible in that view
- If omitted, shows all queue items

### Output Format

The API returns a plaintext response with one job display name per line:
```
Project A
Project B
Project C
```

### Queue Order

The API returns the **sorted queue** order, which reflects:
- The current state after any manual reordering done through the Simple Queue plugin
- The actual execution order that Jenkins will follow
- All move operations (UP, DOWN, TOP, BOTTOM) that have been applied

### Examples

#### Get buildable items (default)
```bash
# Safe API (requires CSRF token)
curl -X POST --user username:apitoken \
"http://${JENKINS_URL}/simpleMove/printQueue"

# Unsafe API (no CSRF token needed, must be enabled)
curl "http://${JENKINS_URL}/simpleMoveUnsafe/printQueue"
```

#### Get all items (including non-buildable)
```bash
# Safe API
curl -X POST --user username:apitoken \
"http://${JENKINS_URL}/simpleMove/printQueue?buildable=false"

# Unsafe API
curl "http://${JENKINS_URL}/simpleMoveUnsafe/printQueue"
```

#### Get queue for a specific view
```bash
# Safe API
curl -X POST --user username:apitoken \
"http://${JENKINS_URL}/simpleMove/printQueue?viewName=my_view"

# Unsafe API
curl "http://${JENKINS_URL}/simpleMoveUnsafe/printQueue?viewName=my_view"
```

#### Using in scripts
```bash
#!/bin/bash
# Get the queue and process each job
curl -s --user username:apitoken \
"http://${JENKINS_URL}/simpleMove/printQueue" | \
while IFS= read -r job_name; do
echo "Processing: $job_name"
# Your processing logic here
done
```

```bash
curl "http://${JENKINS_URL}/simpleMoveUnsafe/printQueue" | wc -l

```

### Security Considerations

- The **safe API** (`/simpleMove/printQueue`) requires proper authentication and CSRF protection, making it suitable for web-based integrations
- The **unsafe API** (`/simpleMoveUnsafe/printQueue`) bypasses CSRF protection for easier CLI/automation use, but must be explicitly enabled in the plugin configuration
- Both APIs require appropriate Jenkins permissions
23 changes: 23 additions & 0 deletions src/main/java/cz/mendelu/xotradov/MoveAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,27 @@
response.setStatus(StaplerResponse2.SC_INTERNAL_SERVER_ERROR);
}
}

/**
* Print the current queue as plaintext list
* @param request Stapler request from user
* @param response Stapler response send back to users browser
*/
@RequirePOST
public void doPrintQueue(final StaplerRequest2 request, final StaplerResponse2 response) {
Jenkins j = Jenkins.get();
if (!j.hasPermission(PermissionHandler.SIMPLE_QUEUE_MOVE_PERMISSION)) {

Check warning on line 72 in src/main/java/cz/mendelu/xotradov/MoveAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 72 is only partially covered, one branch is missing
response.setStatus(StaplerResponse2.SC_FORBIDDEN);
return;

Check warning on line 74 in src/main/java/cz/mendelu/xotradov/MoveAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 73-74 are not covered by tests
}
try {
Queue queue = j.getQueue();
if (queue != null) {

Check warning on line 78 in src/main/java/cz/mendelu/xotradov/MoveAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 78 is only partially covered, one branch is missing
printQueueImpl(request, response, queue, j);
}
} catch (Exception e) {
logger.warning(e.toString());
response.setStatus(StaplerResponse2.SC_INTERNAL_SERVER_ERROR);

Check warning on line 83 in src/main/java/cz/mendelu/xotradov/MoveAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 81-83 are not covered by tests
}
}
}
43 changes: 43 additions & 0 deletions src/main/java/cz/mendelu/xotradov/MoveActionWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -1185,4 +1185,47 @@
private void resort(Queue queue) {
queue.getSorter().sortBuildableItems(queue.getBuildableItems());
}

/**
* Print the current queue state as plaintext list
* @param request Stapler request from user
* @param response Stapler response to write the queue information
* @param queue The Jenkins queue
* @param jenkins Jenkins instance
* @throws IOException if writing to response fails
*/
protected void printQueueImpl(StaplerRequest2 request, StaplerResponse2 response, Queue queue, Jenkins jenkins) throws IOException {
response.setContentType("text/plain; charset=UTF-8");
response.setStatus(StaplerResponse2.SC_OK);
PrintWriter writer = response.getWriter();

if (writer != null) {

Check warning on line 1202 in src/main/java/cz/mendelu/xotradov/MoveActionWorker.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1202 is only partially covered, one branch is missing
String buildableParam = request.getParameter("buildable");
boolean onlyBuildable = buildableParam == null || !buildableParam.equalsIgnoreCase("false");

// Get view if specified
String viewName = request.getParameter(VIEW_NAME_PARAM_NAME);
View view = viewName != null ? jenkins.getView(viewName) : null;

Check warning on line 1208 in src/main/java/cz/mendelu/xotradov/MoveActionWorker.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1208 is only partially covered, one branch is missing

// Get queue items - either from view or from full queue
Collection<Queue.Item> queueItems;
if (view != null && view.isFilterQueue()) {

Check warning on line 1212 in src/main/java/cz/mendelu/xotradov/MoveActionWorker.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1212 is only partially covered, 3 branches are missing
queueItems = view.getQueueItems();

Check warning on line 1213 in src/main/java/cz/mendelu/xotradov/MoveActionWorker.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 1213 is not covered by tests
} else {
queueItems = Arrays.asList(queue.getItems());
}

for (Queue.Item item : queueItems) {
if (item.task != null) {

Check warning on line 1219 in src/main/java/cz/mendelu/xotradov/MoveActionWorker.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1219 is only partially covered, one branch is missing
if (onlyBuildable) {
if (item.isBuildable()) {

Check warning on line 1221 in src/main/java/cz/mendelu/xotradov/MoveActionWorker.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1221 is only partially covered, one branch is missing
writer.println(item.task.getDisplayName());
}
} else {
writer.println(item.task.getDisplayName());
}
}
}
}
}
}
21 changes: 21 additions & 0 deletions src/main/java/cz/mendelu/xotradov/UnsafeMoveAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,25 @@
response.setStatus(StaplerResponse2.SC_INTERNAL_SERVER_ERROR);
}
}

/**
* Print the current queue as plaintext list (unsafe - no CSRF protection)
* @param request Stapler request from user
* @param response Stapler response send back to users browser
*/
public void doPrintQueue(final StaplerRequest2 request, final StaplerResponse2 response) {
if (!SimpleQueueConfig.getInstance().isEnableUnsafe()) {
throw new IllegalArgumentException("Unsafe print queue api attempted without being enabled");
}
Jenkins j = Jenkins.get();
try {
Queue queue = j.getQueue();
if (queue != null) {

Check warning on line 62 in src/main/java/cz/mendelu/xotradov/UnsafeMoveAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 62 is only partially covered, one branch is missing
printQueueImpl(request, response, queue, j);
}
} catch (Exception e) {
logger.warning(e.toString());
response.setStatus(StaplerResponse2.SC_INTERNAL_SERVER_ERROR);

Check warning on line 67 in src/main/java/cz/mendelu/xotradov/UnsafeMoveAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 65-67 are not covered by tests
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package cz.mendelu.xotradov.test.moves;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

import cz.mendelu.xotradov.MoveAction;
import cz.mendelu.xotradov.test.TestHelper;
import hudson.model.FreeStyleProject;
import hudson.model.Queue;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.StaplerResponse2;
import org.mockito.Mockito;

@WithJenkins
public class MoveAction_doPrintQueueTest {

private TestHelper helper;

@AfterEach
public void waitForClean() throws Exception {
helper.cleanup();
}

@Test
public void doPrintQueueWithBuildableItems(JenkinsRule jenkinsRule) throws Exception {
helper = new TestHelper(jenkinsRule);
try {
long maxTestTime = 10000;
helper.fillQueueFor(maxTestTime);
FreeStyleProject C = helper.createAndSchedule("C", maxTestTime);
FreeStyleProject D = helper.createAndSchedule("D", maxTestTime);
Queue queue = jenkinsRule.jenkins.getQueue();

MoveAction moveAction = helper.getMoveAction();
StaplerRequest2 request = Mockito.mock(StaplerRequest2.class);
StaplerResponse2 response = Mockito.mock(StaplerResponse2.class);

StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(writer);
when(request.getParameter("buildable")).thenReturn(null); // default to true

moveAction.doPrintQueue(request, response);
writer.flush();

String output = stringWriter.toString();
assertTrue(output.contains("D"), "Output should contain project D");
assertTrue(output.contains("C"), "Output should contain project C");

// Verify format - each project on its own line
String[] lines = output.trim().split("\n");
assertEquals(2, lines.length, "Should have 2 buildable items");
assertEquals("D", lines[0].trim());
assertEquals("C", lines[1].trim());
} catch (Exception e) {
fail("Test failed with exception: " + e.getMessage());
}
}

@Test
public void doPrintQueueWithBuildableParamTrue(JenkinsRule jenkinsRule) throws Exception {
helper = new TestHelper(jenkinsRule);
try {
long maxTestTime = 10000;
helper.fillQueueFor(maxTestTime);
FreeStyleProject C = helper.createAndSchedule("C", maxTestTime);
FreeStyleProject D = helper.createAndSchedule("D", maxTestTime);

MoveAction moveAction = helper.getMoveAction();
StaplerRequest2 request = Mockito.mock(StaplerRequest2.class);
StaplerResponse2 response = Mockito.mock(StaplerResponse2.class);

StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(writer);
when(request.getParameter("buildable")).thenReturn("true");

moveAction.doPrintQueue(request, response);
writer.flush();

String output = stringWriter.toString();
assertTrue(output.contains("D"), "Output should contain project D");
assertTrue(output.contains("C"), "Output should contain project C");
} catch (Exception e) {
fail("Test failed with exception: " + e.getMessage());
}
}

@Test
public void doPrintQueueWithBuildableParamFalse(JenkinsRule jenkinsRule) throws Exception {
helper = new TestHelper(jenkinsRule);
try {
long maxTestTime = 10000;
helper.fillQueueFor(maxTestTime);
FreeStyleProject C = helper.createAndSchedule("C", maxTestTime);
FreeStyleProject D = helper.createAndSchedule("D", maxTestTime);

MoveAction moveAction = helper.getMoveAction();
StaplerRequest2 request = Mockito.mock(StaplerRequest2.class);
StaplerResponse2 response = Mockito.mock(StaplerResponse2.class);

StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(writer);
when(request.getParameter("buildable")).thenReturn("false");

moveAction.doPrintQueue(request, response);
writer.flush();

String output = stringWriter.toString();
// With buildable=false, should show all items including non-buildable
assertTrue(output.contains("D"), "Output should contain project D");
assertTrue(output.contains("C"), "Output should contain project C");
} catch (Exception e) {
fail("Test failed with exception: " + e.getMessage());
}
}

@Test
public void doPrintQueueEmptyQueue(JenkinsRule jenkinsRule) throws Exception {
helper = new TestHelper(jenkinsRule);
try {
// Don't add any items to queue
Queue queue = jenkinsRule.jenkins.getQueue();
assertEquals(0, queue.getItems().length, "Queue should be empty");

MoveAction moveAction = helper.getMoveAction();
StaplerRequest2 request = Mockito.mock(StaplerRequest2.class);
StaplerResponse2 response = Mockito.mock(StaplerResponse2.class);

StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(writer);
when(request.getParameter("buildable")).thenReturn(null);

moveAction.doPrintQueue(request, response);
writer.flush();

String output = stringWriter.toString();
assertTrue(output.trim().isEmpty(), "Output should be empty for empty queue");
} catch (Exception e) {
fail("Test failed with exception: " + e.getMessage());
}
}
}

// Made with Bob
Loading
Loading