Skip to content

Commit

Permalink
feat: add caching feature to jobfinder (refs #272)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasbjerre committed Aug 19, 2023
1 parent ef3289a commit 087ac6a
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 20 deletions.
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,22 @@ The token can be supplied as a:

- Request parameter:

`curl -vs http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=abc123 2>&1`
`curl -vs "http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=abc123" 2>&1`
- Token header:

`curl -vs -H "token: abc123" http://localhost:8080/jenkins/generic-webhook-trigger/invoke 2>&1`
`curl -vs -H "token: abc123" "http://localhost:8080/jenkins/generic-webhook-trigger/invoke" 2>&1`
- It will also detect `X-Gitlab-Token`.
- _Authorization_ header of type _Bearer_ :

`curl -vs -H "Authorization: Bearer abc123" http://localhost:8080/jenkins/generic-webhook-trigger/invoke 2>&1`
`curl -vs -H "Authorization: Bearer abc123" "http://localhost:8080/jenkins/generic-webhook-trigger/invoke" 2>&1`

### Cache parameter

There is a special `usecache` parameter. When supplied, the plugin will cache configured jobs for `5` minutes. After the first invocation, useing this parameter, the plugin will automatically refresh the cache every `5` minute so that any future calls will use the cached value. This means the effect of any changes to any configured job will be delayed for `5` minutes.

The cache will be used when `usecache=true` parameter is passed like this:

`curl -vs "http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=abc123&usecache=true" 2>&1`

## Trigger exactly one build

Expand Down Expand Up @@ -106,26 +114,26 @@ If you are fiddling with expressions, you may want to checkout:
It's probably easiest to do with `curl`. Given that you have configured a Jenkins job to trigger on Generic Webhook, here are some examples of how to start the jobs.

```bash
curl -vs http://localhost:8080/jenkins/generic-webhook-trigger/invoke 2>&1
curl -vs "http://localhost:8080/jenkins/generic-webhook-trigger/invoke" 2>&1
```

This should start your job, if the job has no `token` configured and no security enabled. If you have security enabled you may need to authenticate:

```bash
curl -vs http://theusername:thepasssword@localhost:8080/jenkins/generic-webhook-trigger/invoke 2>&1
curl -vs "http://theusername:thepasssword@localhost:8080/jenkins/generic-webhook-trigger/invoke" 2>&1
```

If your job has a `token` you don't need to supply other credentials. You can specify the `token` like this:

```bash
curl -vs http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=TOKEN_HERE 2>&1
curl -vs "http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=TOKEN_HERE" 2>&1
```
Please keep in mind, using a token always runs the triggered jobs with SYSTEM privileges.

If you want to trigger with `token` and some post content, `curl` can dot that like this.

```bash
curl -v -H "Content-Type: application/json" -X POST -d '{ "app":{ "name":"some value" }}' http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=TOKEN_HERE
curl -v -H "Content-Type: application/json" -X POST -d '{ "app":{ "name":"some value" }}' "http://localhost:8080/jenkins/generic-webhook-trigger/invoke?token=TOKEN_HERE"
```

## Screenshots
Expand Down Expand Up @@ -372,7 +380,7 @@ pipeline {
It can be triggered with something like:

```bash
curl -X POST -H "Content-Type: application/json" -H "headerWithNumber: nbr123" -H "headerWithString: a b c" -d '{ "before": "1848f12", "after": "5cab1", "ref": "refs/heads/develop" }' -vs http://admin:admin@localhost:8080/jenkins/generic-webhook-trigger/invoke?requestWithNumber=nbr%20123\&requestWithString=a%20string
curl -X POST -H "Content-Type: application/json" -H "headerWithNumber: nbr123" -H "headerWithString: a b c" -d '{ "before": "1848f12", "after": "5cab1", "ref": "refs/heads/develop" }' -vs "http://admin:admin@localhost:8080/jenkins/generic-webhook-trigger/invoke?requestWithNumber=nbr%20123&requestWithString=a%20string"
```

And the job will have this in the log:
Expand Down
10 changes: 7 additions & 3 deletions debug.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/bin/sh
mvn versions:update-properties
mvnDebug -q hpi:run -Djava.util.logging.config.file=logging.properties -Djenkins.version=2.204.1 -Denforcer.skip=true

./mvnw versions:update-properties
MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n" \
./mvnw hpi:run \
-Djava.util.logging.config.file=logging.properties \
-Djenkins.version=2.361.4 \
-Denforcer.skip=true \
-Dhudson.model.ParametersAction.keepUndefinedParameters=true
8 changes: 6 additions & 2 deletions run.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#!/bin/sh
mvn versions:update-properties
mvn hpi:run -Djava.util.logging.config.file=logging.properties -Djenkins.version=2.346.3 -Denforcer.skip=true
./mvnw versions:update-properties
./mvnw hpi:run \
-Djava.util.logging.config.file=logging.properties \
-Djenkins.version=2.361.4 \
-Denforcer.skip=true \
-Dhudson.model.ParametersAction.keepUndefinedParameters=true
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ HttpResponse doInvoke(
final String postContent,
final String givenToken) {

final List<FoundJob> foundJobs = JobFinder.findAllJobsWithTrigger(givenToken);
final boolean useCache = this.shouldUseCache(parameterMap);

Check warning on line 173 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 173 is not covered by tests
final List<FoundJob> foundJobs = JobFinder.findAllJobsWithTrigger(givenToken, useCache);

Check warning on line 174 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 174 is not covered by tests
final Map<String, Object> triggerResultsMap = new HashMap<>();
boolean allSilent = true;
boolean errors = false;
Expand Down Expand Up @@ -213,6 +214,21 @@ HttpResponse doInvoke(
}
}

private boolean shouldUseCache(final Map<String, String[]> parameterMap) {
if (parameterMap == null) {

Check warning on line 218 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 218 is only partially covered, 2 branches are missing
return false;

Check warning on line 219 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 219 is not covered by tests
}
final String useCacheParamName = "usecache";

Check warning on line 221 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 221 is not covered by tests
if (parameterMap.containsKey(useCacheParamName)) {

Check warning on line 222 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 222 is only partially covered, 2 branches are missing
final String[] useCache = parameterMap.get(useCacheParamName);

Check warning on line 223 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 223 is not covered by tests
if (useCache.length == 1) {

Check warning on line 224 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 224 is only partially covered, 2 branches are missing
final String useCacheValue = useCache[0];

Check warning on line 225 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 225 is not covered by tests
return useCacheValue != null && (useCacheValue.equalsIgnoreCase("true"));

Check warning on line 226 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 226 is only partially covered, 4 branches are missing
}
}
return false;

Check warning on line 229 in src/main/java/org/jenkinsci/plugins/gwt/GenericWebHookRequestReceiver.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 229 is not covered by tests
}

String createMessageFromException(final Throwable t) {
String stacktraceInfo = "";
if (t.getStackTrace().length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public final class JobFinder {

private static Logger LOG = Logger.getLogger(JobFinder.class.getSimpleName());
private static Logger LOG = Logger.getLogger(JobFinder.class.getName());

private JobFinder() {}

Expand All @@ -33,13 +33,14 @@ static void setJobFinderImpersonater(final JobFinderImpersonater jobFinderImpers
JobFinder.jobFinderImpersonater = jobFinderImpersonater;
}

public static List<FoundJob> findAllJobsWithTrigger(final String givenToken) {
public static List<FoundJob> findAllJobsWithTrigger(
final String givenToken, final boolean useCache) {

final List<FoundJob> found = new ArrayList<>();

final boolean impersonate = !isNullOrEmpty(givenToken);
final List<ParameterizedJob> candidateProjects =
jobFinderImpersonater.getAllParameterizedJobs(impersonate);
jobFinderImpersonater.getAllParameterizedJobs(impersonate, useCache);
for (final ParameterizedJob candidateJob : candidateProjects) {
final GenericTrigger genericTriggerOpt = findGenericTrigger(candidateJob.getTriggers());
if (genericTriggerOpt != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,72 @@
package org.jenkinsci.plugins.gwt.jobfinder;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import hudson.security.ACL;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.model.ParameterizedJobMixIn.ParameterizedJob;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;

public class JobFinderImpersonater {
public List<ParameterizedJob> getAllParameterizedJobs(boolean impersonate) {
private static final int CACHE_MINUTES = 5;

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

private final LoadingCache<Boolean, List<ParameterizedJob>> cache;

public JobFinderImpersonater() {
this.cache =
CacheBuilder.newBuilder() //
.refreshAfterWrite(Duration.ofMinutes(CACHE_MINUTES)) //
.build(
new CacheLoader<Boolean, List<ParameterizedJob>>() {
@Override
public List<ParameterizedJob> load(final Boolean impersonate) throws Exception {
LOGGER.log(Level.FINE, "Loading the cache with impersonate " + impersonate);

Check warning on line 34 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 34 is not covered by tests
return doGetAllParameterizedJobs(impersonate);

Check warning on line 35 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 35 is not covered by tests
}
});
Executors.newSingleThreadScheduledExecutor()
.scheduleWithFixedDelay(
() -> {
LOGGER.log(Level.FINE, "Triggering cache refresh");
this.cache
.asMap()
.keySet()
.parallelStream()
.forEach((key) -> this.cache.refresh(key));
},
0,
CACHE_MINUTES,
TimeUnit.MINUTES);
}

public List<ParameterizedJob> getAllParameterizedJobs(
final boolean impersonate, final boolean useCache) {
if (useCache) {

Check warning on line 55 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 55 is only partially covered, 2 branches are missing
try {
LOGGER.log(Level.FINE, "Using the cache");

Check warning on line 57 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 57 is not covered by tests
return this.cache.get(impersonate);

Check warning on line 58 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 58 is not covered by tests
} catch (final ExecutionException e) {

Check warning on line 59 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 59 is not covered by tests
LOGGER.log(Level.SEVERE, "Was unable to getAllParameterizedJobs from cache.", e);

Check warning on line 60 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 60 is not covered by tests
LOGGER.log(Level.FINE, "Not using the cache");

Check warning on line 61 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 61 is not covered by tests
return doGetAllParameterizedJobs(impersonate);

Check warning on line 62 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 62 is not covered by tests
}
}
LOGGER.log(Level.FINE, "Not using the cache");

Check warning on line 65 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 65 is not covered by tests
return doGetAllParameterizedJobs(impersonate);

Check warning on line 66 in src/main/java/org/jenkinsci/plugins/gwt/jobfinder/JobFinderImpersonater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 66 is not covered by tests
}

private static List<ParameterizedJob> doGetAllParameterizedJobs(final boolean impersonate) {
SecurityContext orig = null;
try {
if (impersonate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public void before() {
final JobFinderImpersonater jobFinderImpersonater =
new JobFinderImpersonater() {
@Override
public List<ParameterizedJob> getAllParameterizedJobs(final boolean impersonate) {
public List<ParameterizedJob> getAllParameterizedJobs(
final boolean impersonate, final boolean usecache) {
JobFinderTest.this.didImpersonate = impersonate;
return JobFinderTest.this.allParameterizedJobsByImpersonation;
}
Expand Down Expand Up @@ -71,7 +72,7 @@ private ParameterizedJob createJob(final String authToken, final String genericT
}

private List<String> findAllJobs(final String givenToken) {
final List<FoundJob> foundJobs = JobFinder.findAllJobsWithTrigger(givenToken);
final List<FoundJob> foundJobs = JobFinder.findAllJobsWithTrigger(givenToken, false);
final List<String> names = new ArrayList<>();
for (final FoundJob found : foundJobs) {
names.add(found.getFullName());
Expand Down

0 comments on commit 087ac6a

Please sign in to comment.