bbende commented on code in PR #9017:
URL: https://github.com/apache/nifi/pull/9017#discussion_r1664639608
##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java:
##########
@@ -2411,6 +2429,289 @@ public Date getEndDate() {
}
}
+ // ------------
+ // NARs
+ // ------------
+
+ @POST
+ @Consumes(MediaType.APPLICATION_OCTET_STREAM)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("nar-manager/upload")
+ @Operation(
+ summary = "Uploads a NAR and requests for it to be installed",
+ responses = @ApiResponse(content = @Content(schema =
@Schema(implementation = NarSummaryEntity.class))),
+ security = {
+ @SecurityRequirement(name = "Write - /controller")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @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 = "409", description = "The
request was valid but NiFi was not in the appropriate state to process it.")
+ }
+ )
+ public Response uploadNar(
+ @HeaderParam(UploadRequestReplicator.FILENAME_HEADER)
+ final String filename,
+ @Parameter(description = "The contents of the NAR file.", required
= true)
+ final InputStream inputStream) throws IOException {
+
+ authorizeController(RequestAction.WRITE);
+
+ if (StringUtils.isBlank(filename)) {
+ throw new
IllegalArgumentException(UploadRequestReplicator.FILENAME_HEADER + " header is
required");
+ }
+ if (inputStream == null) {
+ throw new IllegalArgumentException("NAR contents are required");
+ }
+
+ // If clustered and not all nodes are connected, do not allow
uploading a NAR.
+ // Generally, we allow the flow to be modified when nodes are
disconnected, but we do not allow uploading a NAR because
+ // the cluster has no mechanism for synchronizing those NARs after the
upload.
+ final ClusterCoordinator clusterCoordinator = getClusterCoordinator();
+ if (clusterCoordinator != null) {
+ final Set<NodeIdentifier> disconnectedNodes =
clusterCoordinator.getNodeIdentifiers(NodeConnectionState.CONNECTING,
NodeConnectionState.DISCONNECTED, NodeConnectionState.DISCONNECTING);
+ if (!disconnectedNodes.isEmpty()) {
+ throw new IllegalStateException("Cannot upload NAR because the
following %s nodes are not currently connected:
%s".formatted(disconnectedNodes.size(), disconnectedNodes));
+ }
+ }
+
+ final long startTime = System.currentTimeMillis();
+ final InputStream maxLengthInputStream = new
MaxLengthInputStream(inputStream, (long) DataUnit.GB.toB(1));
+
+ if (isReplicateRequest()) {
+ final UploadRequest<NarSummaryEntity> uploadRequest = new
UploadRequest.Builder<NarSummaryEntity>()
+ .user(NiFiUserUtils.getNiFiUser())
+ .filename(filename)
+ .identifier(UUID.randomUUID().toString())
+ .contents(maxLengthInputStream)
+ .exampleRequestUri(getAbsolutePath())
+ .responseClass(NarSummaryEntity.class)
+ .build();
+ final NarSummaryEntity summaryEntity =
uploadRequestReplicator.upload(uploadRequest);
+ return generateOkResponse(summaryEntity).build();
+ }
+
+ final NarSummaryEntity summaryEntity =
serviceFacade.uploadNar(maxLengthInputStream);
+ final NarSummaryDTO summary = summaryEntity.getNarSummary();
+
+ final long elapsedTime = System.currentTimeMillis() - startTime;
+ LOGGER.info("Upload completed for NAR [{}] in {} ms",
summary.getIdentifier(), elapsedTime);
+
+ return generateOkResponse(summaryEntity).build();
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/nar-manager/nars")
+ @Operation(
+ summary = "Retrieves summary information for installed NARs",
+ responses = @ApiResponse(content = @Content(schema =
@Schema(implementation = NarSummariesEntity.class))),
+ security = {
+ @SecurityRequirement(name = "Read - /controller")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @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 = "409", description = "The
request was valid but NiFi was not in the appropriate state to process it.")
+ }
+ )
+ public Response getNarSummaries() {
+ authorizeController(RequestAction.READ);
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NarSummariesEntity summariesEntity = new NarSummariesEntity();
+ summariesEntity.setNarSummaries(serviceFacade.getNarSummaries());
+ summariesEntity.setCurrentTime(new Date());
+
+ return generateOkResponse(summariesEntity).build();
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/nar-manager/nars/{id}")
+ @Operation(
+ summary = "Retrieves the summary information for the NAR with the
given identifier",
+ responses = @ApiResponse(content = @Content(schema =
@Schema(implementation = NarDetailsEntity.class))),
+ security = {
+ @SecurityRequirement(name = "Read - /controller")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @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 = "409", description = "The
request was valid but NiFi was not in the appropriate state to process it.")
+ }
+ )
+ public Response getNarSummary(
+ @PathParam("id")
+ @Parameter(description = "The id of the NAR.", required = true)
+ final String id) {
+ authorizeController(RequestAction.READ);
+
+ if (StringUtils.isBlank(id)) {
+ throw new IllegalArgumentException("Id is required");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NarSummaryEntity summaryEntity = serviceFacade.getNarSummary(id);
+ return generateOkResponse(summaryEntity).build();
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/nar-manager/nars/{id}/details")
+ @Operation(
+ summary = "Retrieves the component types available from the
installed NARs",
+ responses = @ApiResponse(content = @Content(schema =
@Schema(implementation = NarDetailsEntity.class))),
+ security = {
+ @SecurityRequirement(name = "Read - /controller")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @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 = "409", description = "The
request was valid but NiFi was not in the appropriate state to process it.")
+ }
+ )
+ public Response getNarDetails(
+ @PathParam("id")
+ @Parameter(description = "The id of the NAR.", required = true)
+ final String id) {
+ authorizeController(RequestAction.READ);
+
+ if (StringUtils.isBlank(id)) {
+ throw new IllegalArgumentException("Id is required");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NarDetailsEntity detailsEntity = serviceFacade.getNarDetails(id);
+ return generateOkResponse(detailsEntity).build();
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ @Path("/nar-manager/nars/{id}/download")
Review Comment:
We already have a `GET` for `nar-manager/nars/{id}` which returns the
summary for the NAR with the given id.
I guess then can be differentiated by the requested content type?
We also have similar examples like downloading a flow definition vs
retrieving JSON for a process process group:
```
/process-groups/{id}/download
/process-groups/{id}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]