This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new 637e68d115 [SYNCOPE-1947] Job actuator endpoint
637e68d115 is described below
commit 637e68d1150a3633add2fc92d04a2147a86104ec
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Tue Feb 3 10:43:29 2026 +0100
[SYNCOPE-1947] Job actuator endpoint
---
.../client/console/SyncopeConsoleApplication.java | 3 +-
.../syncope/client/console/widgets/JobWidget.java | 7 +-
.../client/enduser/SyncopeWebApplication.java | 3 +-
.../java/job/SyncopeTaskScheduler.java | 10 ++-
.../src/test/resources/core-debug.properties | 2 +-
.../core/starter/SyncopeCoreApplication.java | 8 ++
.../syncope/core/starter/actuate/JobEndpoint.java | 87 ++++++++++++++++++++++
core/starter/src/main/resources/core.properties | 2 +-
.../src/main/resources/core-embedded.properties | 2 +-
.../asciidoc/reference-guide/concepts/routes.adoc | 4 +-
.../asciidoc/reference-guide/usage/actuator.adoc | 7 +-
11 files changed, 117 insertions(+), 18 deletions(-)
diff --git
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
index a92b9227f4..d58f35f5e8 100644
---
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
+++
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
@@ -89,8 +89,7 @@ public class SyncopeConsoleApplication extends
SpringBootServletInitializer {
final Cache<String, OffsetDateTime> loggedoutSessionIdCache,
@Qualifier(SyncopeWebApplication.DESTROYED_SESSIONID_CACHE)
final Cache<String, OffsetDateTime> destroyedSessionIdCache,
- final DynamicMenuStringResourceLoader
dynamicMenuStringResourceLoader
- ) {
+ final DynamicMenuStringResourceLoader
dynamicMenuStringResourceLoader) {
return new SyncopeWebApplication(
props,
diff --git
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index bce5877aa9..635a45017f 100644
---
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -213,10 +213,7 @@ public class JobWidget extends BaseWidget {
List<JobTO> updatedAvailable = new ArrayList<>();
if
(SyncopeConsoleSession.get().owns(IdRepoEntitlement.NOTIFICATION_LIST)) {
- JobTO notificationJob = notificationRestClient.getJob();
- if (notificationJob != null) {
- updatedAvailable.add(notificationJob);
- }
+
Optional.ofNullable(notificationRestClient.getJob()).ifPresent(updatedAvailable::add);
}
if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.TASK_LIST)) {
updatedAvailable.addAll(taskRestClient.listJobs());
@@ -425,6 +422,8 @@ public class JobWidget extends BaseWidget {
taskType = TaskType.SCHEDULED;
} else if (jobTO.getRefDesc().startsWith("PULL")) {
taskType = TaskType.PULL;
+ } else if
(jobTO.getRefDesc().startsWith("LIVE_SYNC")) {
+ taskType = TaskType.LIVE_SYNC;
} else if (jobTO.getRefDesc().startsWith("PUSH")) {
taskType = TaskType.PUSH;
} else if (jobTO.getRefDesc().startsWith("MACRO"))
{
diff --git
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
index 97dcf13ccb..9318e46496 100644
---
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
+++
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
@@ -101,8 +101,7 @@ public class SyncopeWebApplication extends
WicketBootSecuredWebApplication imple
final ClassPathScanImplementationLookup lookup,
final ServiceOps serviceOps,
final List<IResource> resources,
- final DynamicMenuStringResourceLoader
dynamicMenuStringResourceLoader
- ) {
+ final DynamicMenuStringResourceLoader
dynamicMenuStringResourceLoader) {
this.resourceLoader = resourceLoader;
this.props = props;
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java
index 804a0323ed..413f2335dd 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java
@@ -39,16 +39,14 @@ import org.springframework.scheduling.support.CronTrigger;
public class SyncopeTaskScheduler {
- protected record Key(String domain, String job) {
+ public record Key(String domain, String job) {
}
- protected record Value(Job job, Optional<ScheduledFuture<?>> instant,
Optional<ScheduledFuture<?>> cron) {
+ public record Value(Job job, Optional<ScheduledFuture<?>> instant,
Optional<ScheduledFuture<?>> cron) {
}
- public static final String CACHE = "jobCache";
-
protected static final Logger LOG =
LoggerFactory.getLogger(SyncopeTaskScheduler.class);
protected final TaskScheduler scheduler;
@@ -152,4 +150,8 @@ public class SyncopeTaskScheduler {
public List<String> getJobNames(final String domain) {
return jobs.keySet().stream().filter(key ->
domain.equals(key.domain())).map(Key::job).toList();
}
+
+ public Map<Key, Value> getJobs() {
+ return Map.copyOf(jobs);
+ }
}
diff --git
a/core/self-keymaster-starter/src/test/resources/core-debug.properties
b/core/self-keymaster-starter/src/test/resources/core-debug.properties
index 4faa0d847c..8af477ddff 100644
--- a/core/self-keymaster-starter/src/test/resources/core-debug.properties
+++ b/core/self-keymaster-starter/src/test/resources/core-debug.properties
@@ -20,7 +20,7 @@ service.discovery.address=http://localhost:9080/syncope/rest/
logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
-management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,metrics
+management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,job,metrics
keymaster.address=http://localhost:9080/syncope/rest/keymaster
keymaster.username=${anonymousUser}
diff --git
a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
index e5f3c5e588..45c89a2960 100644
---
a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
+++
b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
@@ -42,10 +42,12 @@ import
org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
import org.apache.syncope.core.provisioning.api.ConnectorManager;
import org.apache.syncope.core.provisioning.api.ImplementationLookup;
import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
+import org.apache.syncope.core.provisioning.java.job.SyncopeTaskScheduler;
import
org.apache.syncope.core.starter.actuate.DefaultSyncopeCoreInfoContributor;
import org.apache.syncope.core.starter.actuate.DomainsHealthIndicator;
import org.apache.syncope.core.starter.actuate.EntityCacheEndpoint;
import
org.apache.syncope.core.starter.actuate.ExternalResourcesHealthIndicator;
+import org.apache.syncope.core.starter.actuate.JobEndpoint;
import org.apache.syncope.core.starter.actuate.SyncopeCoreInfoContributor;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -197,6 +199,12 @@ public class SyncopeCoreApplication extends
SpringBootServletInitializer {
return new EntityCacheEndpoint(entityCacheDAO);
}
+ @ConditionalOnMissingBean
+ @Bean
+ public JobEndpoint jobEndpoint(final SyncopeTaskScheduler
syncopeTaskScheduler) {
+ return new JobEndpoint(syncopeTaskScheduler);
+ }
+
@Bean
public SyncopeStarterEventListener syncopeCoreEventListener(
@Qualifier("syncopeCoreInfoContributor")
diff --git
a/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/JobEndpoint.java
b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/JobEndpoint.java
new file mode 100644
index 0000000000..ba9508a104
--- /dev/null
+++
b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/JobEndpoint.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.starter.actuate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.apache.syncope.common.lib.types.JobAction;
+import org.apache.syncope.core.provisioning.java.job.SyncopeTaskScheduler;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
+import org.springframework.boot.actuate.endpoint.annotation.Selector;
+import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
+
+@Endpoint(id = "job")
+public class JobEndpoint {
+
+ protected final SyncopeTaskScheduler syncopeTaskScheduler;
+
+ public JobEndpoint(final SyncopeTaskScheduler syncopeTaskScheduler) {
+ this.syncopeTaskScheduler = syncopeTaskScheduler;
+ }
+
+ @ReadOperation
+ public Map<String, Object> status() {
+ Map<String, Object> status = new HashMap<>();
+
+ syncopeTaskScheduler.getJobs().forEach((k, v) -> {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> jobs = (Map<String, Object>)
status.computeIfAbsent(k.domain(), d -> new HashMap<>());
+
+ Map<String, Object> job = new HashMap<>();
+ jobs.put(k.job(), job);
+
+ job.put("executor", v.job().getContext().getExecutor());
+ job.put("dryRun", v.job().getContext().isDryRun());
+ job.put("context", v.job().getContext().getData());
+
+ v.instant().ifPresent(f -> job.put("delay (seconds)",
f.getDelay(TimeUnit.SECONDS)));
+
+ v.cron().ifPresent(f -> {
+ job.put("next schedule (seconds)",
f.getDelay(TimeUnit.SECONDS));
+ job.put("done", f.isDone());
+ job.put("cancelled", f.isCancelled());
+ });
+ });
+
+ return status;
+ }
+
+ @WriteOperation
+ public void action(
+ final @Selector String domain,
+ final @Selector String jobName,
+ final @Selector JobAction action) {
+
+ switch (action) {
+ case START ->
+ syncopeTaskScheduler.start(domain, jobName);
+
+ case STOP ->
+ syncopeTaskScheduler.stop(domain, jobName);
+
+ case DELETE ->
+ syncopeTaskScheduler.delete(domain, jobName);
+
+ default -> {
+ }
+ }
+ }
+}
diff --git a/core/starter/src/main/resources/core.properties
b/core/starter/src/main/resources/core.properties
index 950bf0c65c..23d8724c3c 100644
--- a/core/starter/src/main/resources/core.properties
+++ b/core/starter/src/main/resources/core.properties
@@ -27,7 +27,7 @@ server.servlet.encoding.force=true
server.servlet.contextPath=/syncope
cxf.path=/rest
-management.endpoints.web.exposure.include=health,info,loggers,entityCache
+management.endpoints.web.exposure.include=health,info,loggers,entityCache,job
management.endpoint.health.show-details=ALWAYS
management.endpoint.env.show-values=WHEN_AUTHORIZED
diff --git a/fit/core-reference/src/main/resources/core-embedded.properties
b/fit/core-reference/src/main/resources/core-embedded.properties
index d45dc0dac3..42c2f8c1df 100644
--- a/fit/core-reference/src/main/resources/core-embedded.properties
+++ b/fit/core-reference/src/main/resources/core-embedded.properties
@@ -16,7 +16,7 @@
# under the License.
embedded.databases=syncope,syncopetwo,syncopetest
-management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,metrics
+management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,job,metrics
keymaster.address=http://localhost:9080/syncope/rest/keymaster
keymaster.username=${anonymousUser}
diff --git a/src/main/asciidoc/reference-guide/concepts/routes.adoc
b/src/main/asciidoc/reference-guide/concepts/routes.adoc
index 761e5b100c..22859312ce 100644
--- a/src/main/asciidoc/reference-guide/concepts/routes.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/routes.adoc
@@ -50,7 +50,7 @@ The received response, after being post-processed by matching
route's _filters_,
==== Predicates
Inside Route definition, each predicate will be referring to some Spring Cloud
Gateway's
-https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html[Predicate
factory^]:
+https://docs.spring.io/spring-cloud-gateway/reference/4.3/spring-cloud-gateway-server-webflux/request-predicates-factories.html[Predicate
factory^]:
* `AFTER` matches requests that happen after the specified datetime;
* `BEFORE` matches requests that happen before the specified datetime;
@@ -74,7 +74,7 @@ endif::[]
==== Filters
Inside Route definition, each filter will be referring to some Spring Cloud
Gateway's
-https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html[Filter
factory^]:
+https://docs.spring.io/spring-cloud-gateway/reference/4.3/spring-cloud-gateway-server-webflux/gatewayfilter-factories.html[Filter
factory^]:
* `ADD_REQUEST_HEADER` adds a header to the downstream request's headers;
* `ADD_REQUEST_PARAMETER` adds a parameter too the downstream request's
query string;
diff --git a/src/main/asciidoc/reference-guide/usage/actuator.adoc
b/src/main/asciidoc/reference-guide/usage/actuator.adoc
index 8c00796806..de2414042c 100644
--- a/src/main/asciidoc/reference-guide/usage/actuator.adoc
+++ b/src/main/asciidoc/reference-guide/usage/actuator.adoc
@@ -45,6 +45,11 @@ a| Allows to work with
https://openjpa.apache.org/builds/4.0.1/apache-openjpa/do
* `GET` - shows JPA cache statistics
* `POST {ENABLE,DISABLE,RESET}` - performs the requested operation onto JPA
cache
* `DELETE` - clears JPA cache's current content
+| `job`
+a| Allows to work with the various jobs defined after <<tasks>> and
<<reports>>.
+
+* `GET` - shows the existing job data
+* `POST {START,STOP,DELETE}` - performs the requested action on a given Job
|===
@@ -80,6 +85,6 @@ a|
* `DELETE {id}` - removes the session with given `id`
| `gateway`
-|
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/actuator-api.html[More
details^]
+|
https://docs.spring.io/spring-cloud-gateway/reference/4.3/spring-cloud-gateway-server-webflux/actuator-api.html[More
details^]
|===