From 84d361f110583269393970582c01fa8b96f1b030 Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:41:39 +0700 Subject: [PATCH] feat(status): add configurable resource path for status uploads Mirror DecisionLogs.setResource so operators can point status POSTs at a custom ingest endpoint. Defaults to /status when unset. Fixes #81 Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com> Co-authored-by: Cursor --- .../open_policy_agent/opa/config/Config.java | 23 +++++++- .../opa/plugins/StatusPlugin.java | 20 +++++-- .../opa/config/ConfigTest.java | 1 + .../opa/plugins/StatusPluginTest.java | 58 ++++++++++++++++++- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/opa-services/src/main/java/io/github/open_policy_agent/opa/config/Config.java b/opa-services/src/main/java/io/github/open_policy_agent/opa/config/Config.java index 73ce8dc1..fe943e0f 100644 --- a/opa-services/src/main/java/io/github/open_policy_agent/opa/config/Config.java +++ b/opa-services/src/main/java/io/github/open_policy_agent/opa/config/Config.java @@ -452,6 +452,9 @@ public static class StatusConfig { private Boolean console = false; private String service; + @JsonProperty("resource") + private String resource = "/status"; + public Boolean getConsole() { return console; } @@ -470,9 +473,27 @@ public StatusConfig setService(String service) { return this; } + public String getResource() { + return resource; + } + + public StatusConfig setResource(String resource) { + this.resource = resource; + return this; + } + @Override public String toString() { - return "StatusConfig{" + "console=" + console + ", service='" + service + '\'' + '}'; + return "StatusConfig{" + + "console=" + + console + + ", service='" + + service + + '\'' + + ", resource='" + + resource + + '\'' + + '}'; } } diff --git a/opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/StatusPlugin.java b/opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/StatusPlugin.java index 26ca240a..86066ff3 100644 --- a/opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/StatusPlugin.java +++ b/opa-services/src/main/java/io/github/open_policy_agent/opa/plugins/StatusPlugin.java @@ -62,7 +62,8 @@ public Plugin initialize(PluginManager manager) { plugin.status = new Status(manager, manager.getLogger()) .setConsole(statusConfig.getConsole()) - .setService(statusConfig.getService()); + .setService(statusConfig.getService()) + .setResource(statusConfig.getResource()); } return plugin; @@ -107,6 +108,7 @@ public static class Status { private final Logger logger; private Boolean console; private String service; + private String resource; private Status(PluginManager manager, Logger logger) { this.manager = manager; @@ -131,6 +133,15 @@ public Status setService(String service) { return this; } + public String getResource() { + return resource; + } + + public Status setResource(String resource) { + this.resource = resource; + return this; + } + /** Collect and report current status. */ void reportStatus() { try { @@ -225,9 +236,10 @@ private void sendToService(ObjectNode statusReport) { } try { - // Send status report to service - // Default resource path is /status (same as OPA) - svc.post("/status", statusReport.toString()); + // Determine resource path (default: /status) + String path = (resource != null && !resource.isEmpty()) ? resource : "/status"; + + svc.post(path, statusReport.toString()); logger.debug("Status report sent to service '%s'", service); } catch (Exception e) { logger.error("Failed to send status to service '%s': %s", service, e.getMessage()); diff --git a/opa-services/src/test/java/io/github/open_policy_agent/opa/config/ConfigTest.java b/opa-services/src/test/java/io/github/open_policy_agent/opa/config/ConfigTest.java index 44d95522..d64be422 100644 --- a/opa-services/src/test/java/io/github/open_policy_agent/opa/config/ConfigTest.java +++ b/opa-services/src/test/java/io/github/open_policy_agent/opa/config/ConfigTest.java @@ -292,6 +292,7 @@ void config_statusDefaults() { Config.StatusConfig status = new Config.StatusConfig(); assertFalse(status.getConsole()); + assertEquals("/status", status.getResource()); } @Test diff --git a/opa-services/src/test/java/io/github/open_policy_agent/opa/plugins/StatusPluginTest.java b/opa-services/src/test/java/io/github/open_policy_agent/opa/plugins/StatusPluginTest.java index a55b939a..5f17b338 100644 --- a/opa-services/src/test/java/io/github/open_policy_agent/opa/plugins/StatusPluginTest.java +++ b/opa-services/src/test/java/io/github/open_policy_agent/opa/plugins/StatusPluginTest.java @@ -178,15 +178,63 @@ void configDefaults_consoleIsFalse() { Config.StatusConfig status = new Config.StatusConfig(); assertFalse(status.getConsole()); + assertEquals("/status", status.getResource()); } @Test void configBuilder_setsAllFields() { Config.StatusConfig status = - new Config.StatusConfig().setService("test-service").setConsole(true); + new Config.StatusConfig() + .setService("test-service") + .setConsole(true) + .setResource("/custom/status"); assertEquals("test-service", status.getService()); assertTrue(status.getConsole()); + assertEquals("/custom/status", status.getResource()); + } + + @Test + void initialize_wiresDefaultResourcePath() throws Exception { + Config.StatusConfig status = + new Config.StatusConfig().setService("test-service").setConsole(false); + config.setStatus(status); + + manager = + new PluginManager.Builder() + .withId("test-opa") + .withStore(store) + .withConfig(config) + .withLogger(mockLogger) + .build(); + + StatusPlugin plugin = (StatusPlugin) new StatusPlugin().initialize(manager); + StatusPlugin.Status statusReporter = getStatusReporter(plugin); + + assertEquals("/status", statusReporter.getResource()); + } + + @Test + void initialize_wiresCustomResourcePath() throws Exception { + Config.StatusConfig status = + new Config.StatusConfig() + .setService("test-service") + .setConsole(false) + .setResource("/custom/status"); + config.setStatus(status); + + manager = + new PluginManager.Builder() + .withId("test-opa") + .withStore(store) + .withConfig(config) + .withLogger(mockLogger) + .build(); + + StatusPlugin plugin = (StatusPlugin) new StatusPlugin().initialize(manager); + StatusPlugin.Status statusReporter = getStatusReporter(plugin); + + assertEquals("/custom/status", statusReporter.getResource()); } @Test @@ -382,10 +430,16 @@ void statusReport_includesInstanceId() throws Exception { } private ObjectNode buildStatusReport(StatusPlugin plugin) throws Exception { + return buildStatusReport(getStatusReporter(plugin)); + } + + private static StatusPlugin.Status getStatusReporter(StatusPlugin plugin) throws Exception { java.lang.reflect.Field statusField = StatusPlugin.class.getDeclaredField("status"); statusField.setAccessible(true); - StatusPlugin.Status statusReporter = (StatusPlugin.Status) statusField.get(plugin); + return (StatusPlugin.Status) statusField.get(plugin); + } + private ObjectNode buildStatusReport(StatusPlugin.Status statusReporter) throws Exception { java.lang.reflect.Method buildStatusMethod = StatusPlugin.Status.class.getDeclaredMethod("buildStatusReport"); buildStatusMethod.setAccessible(true);