fix: commands respect selected environment
Backend: AgentRegistryService gains findByApplicationAndEnvironment() and environment-aware addGroupCommandWithReplies() overload. AgentCommandController and ApplicationConfigController accept optional environment query parameter. When set, commands only target agents in that environment. Backward compatible — null means all environments. Frontend: All command mutations (config update, route control, traced processors, tap config, route recording) now pass selectedEnv to the backend via query parameter. Prevents cross-environment command leakage — e.g., updating config for prod no longer pushes to dev agents. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@@ -114,13 +115,14 @@ public class AgentCommandController {
|
||||
@ApiResponse(responseCode = "200", description = "Commands dispatched and responses collected")
|
||||
@ApiResponse(responseCode = "400", description = "Invalid command payload")
|
||||
public ResponseEntity<CommandGroupResponse> sendGroupCommand(@PathVariable String group,
|
||||
@RequestParam(required = false) String environment,
|
||||
@RequestBody CommandRequest request,
|
||||
HttpServletRequest httpRequest) throws JsonProcessingException {
|
||||
CommandType type = mapCommandType(request.type());
|
||||
String payloadJson = request.payload() != null ? objectMapper.writeValueAsString(request.payload()) : "{}";
|
||||
|
||||
Map<String, CompletableFuture<CommandReply>> futures =
|
||||
registryService.addGroupCommandWithReplies(group, type, payloadJson);
|
||||
registryService.addGroupCommandWithReplies(group, environment, type, payloadJson);
|
||||
|
||||
if (futures.isEmpty()) {
|
||||
auditService.log("broadcast_group_command", AuditCategory.AGENT, group,
|
||||
@@ -171,12 +173,18 @@ public class AgentCommandController {
|
||||
description = "Sends a command to all agents currently in LIVE state")
|
||||
@ApiResponse(responseCode = "202", description = "Commands accepted")
|
||||
@ApiResponse(responseCode = "400", description = "Invalid command payload")
|
||||
public ResponseEntity<CommandBroadcastResponse> broadcastCommand(@RequestBody CommandRequest request,
|
||||
public ResponseEntity<CommandBroadcastResponse> broadcastCommand(@RequestParam(required = false) String environment,
|
||||
@RequestBody CommandRequest request,
|
||||
HttpServletRequest httpRequest) throws JsonProcessingException {
|
||||
CommandType type = mapCommandType(request.type());
|
||||
String payloadJson = request.payload() != null ? objectMapper.writeValueAsString(request.payload()) : "{}";
|
||||
|
||||
List<AgentInfo> liveAgents = registryService.findByState(AgentState.LIVE);
|
||||
if (environment != null) {
|
||||
liveAgents = liveAgents.stream()
|
||||
.filter(a -> environment.equals(a.environmentId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> commandIds = new ArrayList<>();
|
||||
for (AgentInfo agent : liveAgents) {
|
||||
|
||||
@@ -91,6 +91,7 @@ public class ApplicationConfigController {
|
||||
description = "Saves config and pushes CONFIG_UPDATE to all LIVE agents of this application")
|
||||
@ApiResponse(responseCode = "200", description = "Config saved and pushed")
|
||||
public ResponseEntity<ConfigUpdateResponse> updateConfig(@PathVariable String application,
|
||||
@RequestParam(required = false) String environment,
|
||||
@RequestBody ApplicationConfig config,
|
||||
Authentication auth,
|
||||
HttpServletRequest httpRequest) {
|
||||
@@ -99,7 +100,7 @@ public class ApplicationConfigController {
|
||||
config.setApplication(application);
|
||||
ApplicationConfig saved = configRepository.save(application, config, updatedBy);
|
||||
|
||||
CommandGroupResponse pushResult = pushConfigToAgents(application, saved);
|
||||
CommandGroupResponse pushResult = pushConfigToAgents(application, environment, saved);
|
||||
log.info("Config v{} saved for '{}', pushed to {} agent(s), {} responded",
|
||||
saved.getVersion(), application, pushResult.total(), pushResult.responded());
|
||||
|
||||
@@ -126,13 +127,16 @@ public class ApplicationConfigController {
|
||||
@ApiResponse(responseCode = "504", description = "Agent did not respond in time")
|
||||
public ResponseEntity<TestExpressionResponse> testExpression(
|
||||
@PathVariable String application,
|
||||
@RequestParam(required = false) String environment,
|
||||
@RequestBody TestExpressionRequest request) {
|
||||
// Find a LIVE agent for this application
|
||||
AgentInfo agent = registryService.findAll().stream()
|
||||
// Find a LIVE agent for this application, optionally filtered by environment
|
||||
var candidates = registryService.findAll().stream()
|
||||
.filter(a -> application.equals(a.applicationId()))
|
||||
.filter(a -> a.state() == AgentState.LIVE)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.filter(a -> a.state() == AgentState.LIVE);
|
||||
if (environment != null) {
|
||||
candidates = candidates.filter(a -> environment.equals(a.environmentId()));
|
||||
}
|
||||
AgentInfo agent = candidates.findFirst().orElse(null);
|
||||
|
||||
if (agent == null) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
@@ -176,7 +180,7 @@ public class ApplicationConfigController {
|
||||
}
|
||||
}
|
||||
|
||||
private CommandGroupResponse pushConfigToAgents(String application, ApplicationConfig config) {
|
||||
private CommandGroupResponse pushConfigToAgents(String application, String environment, ApplicationConfig config) {
|
||||
String payloadJson;
|
||||
try {
|
||||
payloadJson = objectMapper.writeValueAsString(config);
|
||||
@@ -186,7 +190,7 @@ public class ApplicationConfigController {
|
||||
}
|
||||
|
||||
Map<String, CompletableFuture<CommandReply>> futures =
|
||||
registryService.addGroupCommandWithReplies(application, CommandType.CONFIG_UPDATE, payloadJson);
|
||||
registryService.addGroupCommandWithReplies(application, environment, CommandType.CONFIG_UPDATE, payloadJson);
|
||||
|
||||
if (futures.isEmpty()) {
|
||||
return new CommandGroupResponse(true, 0, 0, List.of(), List.of());
|
||||
|
||||
Reference in New Issue
Block a user