This is an automated email from the ASF dual-hosted git repository. markap14 pushed a commit to branch NIFI-15258 in repository https://gitbox.apache.org/repos/asf/nifi.git
commit b7420f7535db880e06b3ddc2823cc734572d659b Author: Matt Gilman <[email protected]> AuthorDate: Thu Jan 15 14:19:10 2026 -0500 NIFI-15453: Adding an endpoint to return all controller services for … (#10770) * NIFI-15453: Adding an endpoint to return all controller services for a process group in a connector. * NIFI-15453: Addressing review feedback. This closes #10770 --- .../org/apache/nifi/web/NiFiServiceFacade.java | 3 + .../apache/nifi/web/StandardNiFiServiceFacade.java | 22 ++++++ .../org/apache/nifi/web/api/ConnectorResource.java | 79 ++++++++++++++++++++++ .../apache/nifi/web/api/TestConnectorResource.java | 34 ++++++++++ 4 files changed, 138 insertions(+) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java index 09761a5076..fba789287f 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java @@ -224,6 +224,9 @@ public interface NiFiServiceFacade { ProcessGroupStatusEntity getConnectorProcessGroupStatus(String id, Boolean recursive); + Set<ControllerServiceEntity> getConnectorControllerServices(String connectorId, String processGroupId, boolean includeAncestorGroups, + boolean includeDescendantGroups, boolean includeReferencingComponents); + void verifyCanVerifyConnectorConfigurationStep(String connectorId, String configurationStepName); List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(String connectorId, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 195aca6e21..ae5e9ae906 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -3721,6 +3721,28 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { return entityFactory.createProcessGroupStatusEntity(dto, permissions); } + @Override + public Set<ControllerServiceEntity> getConnectorControllerServices(final String connectorId, final String processGroupId, + final boolean includeAncestorGroups, final boolean includeDescendantGroups, final boolean includeReferencingComponents) { + final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId); + final ProcessGroup managedProcessGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup(); + final ProcessGroup targetProcessGroup = managedProcessGroup.findProcessGroup(processGroupId); + if (targetProcessGroup == null) { + throw new ResourceNotFoundException("Process Group with ID " + processGroupId + " was not found within Connector " + connectorId); + } + + final Set<ControllerServiceNode> serviceNodes = new HashSet<>(); + serviceNodes.addAll(targetProcessGroup.getControllerServices(includeAncestorGroups)); + + if (includeDescendantGroups) { + serviceNodes.addAll(targetProcessGroup.findAllControllerServices()); + } + + return serviceNodes.stream() + .map(serviceNode -> createControllerServiceEntity(serviceNode, includeReferencingComponents)) + .collect(Collectors.toSet()); + } + @Override public void verifyCanVerifyConnectorConfigurationStep(final String connectorId, final String configurationStepName) { connectorDAO.verifyCanVerifyConfigurationStep(connectorId, configurationStepName); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java index c2f7b4cddf..cb5146afdb 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java @@ -86,6 +86,8 @@ import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity; import org.apache.nifi.web.api.entity.ConnectorEntity; import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity; import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity; +import org.apache.nifi.web.api.entity.ControllerServiceEntity; +import org.apache.nifi.web.api.entity.ControllerServicesEntity; import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity; import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity; import org.apache.nifi.web.api.entity.SearchResultsEntity; @@ -104,6 +106,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; @@ -128,6 +131,7 @@ public class ConnectorResource extends ApplicationResource { private NiFiServiceFacade serviceFacade; private Authorizer authorizer; private FlowResource flowResource; + private ControllerServiceResource controllerServiceResource; private UploadRequestReplicator uploadRequestReplicator; private final RequestManager<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> configVerificationRequestManager = @@ -1411,6 +1415,76 @@ public class ConnectorResource extends ApplicationResource { return generateOkResponse(entity).build(); } + /** + * Retrieves all the controller services in the specified process group within a connector. + * + * @param connectorId The id of the connector + * @param processGroupId The process group id within the connector's hierarchy + * @param includeAncestorGroups Whether to include ancestor process groups + * @param includeDescendantGroups Whether to include descendant process groups + * @param includeReferences Whether to include services' referencing components in the response + * @return A controllerServicesEntity. + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.APPLICATION_JSON) + @Path("/{connectorId}/flow/process-groups/{processGroupId}/controller-services") + @Operation( + summary = "Gets all controller services for a process group within a connector", + responses = { + @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ControllerServicesEntity.class))), + @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(responseCode = "401", description = "Client could not be authenticated."), + @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."), + @ApiResponse(responseCode = "404", description = "The specified resource could not be found."), + @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.") + }, + security = { + @SecurityRequirement(name = "Read - /connectors/{uuid}") + }, + description = "Returns the controller services for the specified process group within the connector's hierarchy. The processGroupId can be " + + "obtained from the managedProcessGroupId field of the ConnectorDTO for the root process group, or from child process " + + "groups within the flow." + ) + public Response getControllerServicesFromConnectorProcessGroup( + @Parameter(description = "The connector id.", required = true) + @PathParam("connectorId") final String connectorId, + @Parameter(description = "The process group id.", required = true) + @PathParam("processGroupId") final String processGroupId, + @Parameter(description = "Whether or not to include parent/ancestor process groups") + @QueryParam("includeAncestorGroups") + @DefaultValue("true") final boolean includeAncestorGroups, + @Parameter(description = "Whether or not to include descendant process groups") + @QueryParam("includeDescendantGroups") + @DefaultValue("false") final boolean includeDescendantGroups, + @Parameter(description = "Whether or not to include services' referencing components in the response") + @QueryParam("includeReferencingComponents") + @DefaultValue("true") final boolean includeReferences) { + + if (isReplicateRequest()) { + return replicate(HttpMethod.GET); + } + + // authorize access to the connector + serviceFacade.authorizeAccess(lookup -> { + final Authorizable connector = lookup.getConnector(connectorId); + connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); + }); + + // get the controller services for the specified process group within the connector's hierarchy + final Set<ControllerServiceEntity> controllerServices = serviceFacade.getConnectorControllerServices( + connectorId, processGroupId, includeAncestorGroups, includeDescendantGroups, includeReferences); + controllerServiceResource.populateRemainingControllerServiceEntitiesContent(controllerServices); + + // create the response entity + final ControllerServicesEntity entity = new ControllerServicesEntity(); + entity.setCurrentTime(new Date()); + entity.setControllerServices(controllerServices); + + // generate the response + return generateOkResponse(entity).build(); + } + /** * Retrieves the status for the process group managed by the specified connector. * @@ -1710,6 +1784,11 @@ public class ConnectorResource extends ApplicationResource { this.flowResource = flowResource; } + @Autowired + public void setControllerServiceResource(final ControllerServiceResource controllerServiceResource) { + this.controllerServiceResource = controllerServiceResource; + } + @Autowired(required = false) public void setUploadRequestReplicator(final UploadRequestReplicator uploadRequestReplicator) { this.uploadRequestReplicator = uploadRequestReplicator; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java index 5c22f82fd1..c218d87d68 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java @@ -36,6 +36,8 @@ import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO; import org.apache.nifi.web.api.entity.ConnectorEntity; import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity; import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity; +import org.apache.nifi.web.api.entity.ControllerServiceEntity; +import org.apache.nifi.web.api.entity.ControllerServicesEntity; import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity; import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.request.ClientIdParameter; @@ -49,6 +51,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.net.URI; import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -91,6 +94,9 @@ public class TestConnectorResource { @Mock private FlowResource flowResource; + @Mock + private ControllerServiceResource controllerServiceResource; + private static final String CONNECTOR_ID = "test-connector-id"; private static final String CONNECTOR_NAME = "Test Connector"; private static final String CONNECTOR_TYPE = "TestConnectorType"; @@ -116,6 +122,7 @@ public class TestConnectorResource { connectorResource.setServiceFacade(serviceFacade); connectorResource.setFlowResource(flowResource); + connectorResource.setControllerServiceResource(controllerServiceResource); connectorResource.httpServletRequest = httpServletRequest; connectorResource.properties = properties; connectorResource.uriInfo = uriInfo; @@ -384,6 +391,33 @@ public class TestConnectorResource { verify(serviceFacade, never()).getConnectorFlow(anyString(), anyString(), eq(false)); } + @Test + public void testGetControllerServicesFromConnectorProcessGroup() { + final Set<ControllerServiceEntity> controllerServices = Set.of(); + when(serviceFacade.getConnectorControllerServices(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true)).thenReturn(controllerServices); + + try (Response response = connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true)) { + assertEquals(200, response.getStatus()); + final ControllerServicesEntity entity = (ControllerServicesEntity) response.getEntity(); + assertEquals(controllerServices, entity.getControllerServices()); + } + + verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class)); + verify(serviceFacade).getConnectorControllerServices(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true); + verify(controllerServiceResource).populateRemainingControllerServiceEntitiesContent(controllerServices); + } + + @Test + public void testGetControllerServicesFromConnectorProcessGroupNotAuthorized() { + doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class)); + + assertThrows(AccessDeniedException.class, () -> + connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true)); + + verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class)); + verify(serviceFacade, never()).getConnectorControllerServices(anyString(), anyString(), eq(true), eq(false), eq(true)); + } + private ConnectorEntity createConnectorEntity() { final ConnectorEntity entity = new ConnectorEntity();
