From ae61ebb5eda7076c6a18ba0419b614b8516faf14 Mon Sep 17 00:00:00 2001 From: Bryan Bende Date: Mon, 9 Oct 2023 10:50:36 -0400 Subject: [PATCH] NIFI-12186 Add ability to export a versioned reporting task snapshot (#7853) * NIFI-12186 Add ability to export a versioned reporting task snapshot - Add CLI commands and optional query param to specify specific reporting task --- .../flow/VersionedReportingTaskSnapshot.java | 48 ++++++ .../flow/mapping/NiFiRegistryFlowMapper.java | 3 +- .../VersionedReportingTaskSnapshotMapper.java | 72 +++++++++ .../apache/nifi/web/NiFiServiceFacade.java | 16 ++ .../nifi/web/StandardNiFiServiceFacade.java | 49 ++++++ .../org/apache/nifi/web/api/FlowResource.java | 151 ++++++++++++++---- .../cli/impl/client/nifi/FlowClient.java | 15 ++ .../client/nifi/impl/JerseyFlowClient.java | 18 +++ .../impl/command/nifi/NiFiCommandGroup.java | 4 + .../nifi/flow/ExportReportingTask.java | 67 ++++++++ .../nifi/flow/ExportReportingTasks.java | 65 ++++++++ .../VersionedReportingTaskSnapshotResult.java | 63 ++++++++ 12 files changed, 538 insertions(+), 33 deletions(-) create mode 100644 nifi-api/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedReportingTaskSnapshotMapper.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTask.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTasks.java create mode 100644 nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/VersionedReportingTaskSnapshotResult.java diff --git a/nifi-api/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java b/nifi-api/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java new file mode 100644 index 0000000000..3da3f8c302 --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/flow/VersionedReportingTaskSnapshot.java @@ -0,0 +1,48 @@ +/* + * 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.nifi.flow; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.List; + +@ApiModel +public class VersionedReportingTaskSnapshot { + + private List reportingTasks; + private List controllerServices; + + @ApiModelProperty(value = "The controller services") + public List getControllerServices() { + return controllerServices; + } + + public void setControllerServices(List controllerServices) { + this.controllerServices = controllerServices; + } + + @ApiModelProperty(value = "The reporting tasks") + public List getReportingTasks() { + return reportingTasks; + } + + public void setReportingTasks(List reportingTasks) { + this.reportingTasks = reportingTasks; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java index f1dfa857fb..b0ac1b8909 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/NiFiRegistryFlowMapper.java @@ -615,8 +615,9 @@ public class NiFiRegistryFlowMapper { continue; } + // if mapping a reporting task, serviceGroupId will be null and we don't want to produce external service references final String serviceGroupId = serviceNode.getProcessGroupIdentifier(); - if (!includedGroupIds.contains(serviceGroupId)) { + if (serviceGroupId != null && !includedGroupIds.contains(serviceGroupId)) { final String serviceId = getId(serviceNode.getVersionedComponentId(), serviceNode.getIdentifier()); final ExternalControllerServiceReference controllerServiceReference = new ExternalControllerServiceReference(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedReportingTaskSnapshotMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedReportingTaskSnapshotMapper.java new file mode 100644 index 0000000000..fb00f48da6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedReportingTaskSnapshotMapper.java @@ -0,0 +1,72 @@ +/* + * 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.nifi.controller.serialization; + +import org.apache.nifi.controller.ReportingTaskNode; +import org.apache.nifi.controller.service.ControllerServiceNode; +import org.apache.nifi.controller.service.ControllerServiceProvider; +import org.apache.nifi.flow.VersionedControllerService; +import org.apache.nifi.flow.VersionedReportingTask; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; +import org.apache.nifi.nar.ExtensionManager; +import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class VersionedReportingTaskSnapshotMapper { + + private final NiFiRegistryFlowMapper flowMapper; + private final ControllerServiceProvider controllerServiceProvider; + + public VersionedReportingTaskSnapshotMapper(final ExtensionManager extensionManager, final ControllerServiceProvider controllerServiceProvider) { + this.flowMapper = new NiFiRegistryFlowMapper(extensionManager); + this.controllerServiceProvider = controllerServiceProvider; + } + + public VersionedReportingTaskSnapshot createMapping(final Set reportingTaskNodes, final Set controllerServiceNodes) { + final VersionedReportingTaskSnapshot versionedReportingTaskSnapshot = new VersionedReportingTaskSnapshot(); + versionedReportingTaskSnapshot.setReportingTasks(mapReportingTasks(reportingTaskNodes)); + versionedReportingTaskSnapshot.setControllerServices(mapControllerServices(controllerServiceNodes)); + return versionedReportingTaskSnapshot; + } + + private List mapReportingTasks(final Set reportingTaskNodes) { + final List reportingTasks = new ArrayList<>(); + + for (final ReportingTaskNode taskNode : reportingTaskNodes) { + final VersionedReportingTask versionedReportingTask = flowMapper.mapReportingTask(taskNode, controllerServiceProvider); + reportingTasks.add(versionedReportingTask); + } + + return reportingTasks; + } + + private List mapControllerServices(final Set controllerServiceNodes) { + final List controllerServices = new ArrayList<>(); + + for (final ControllerServiceNode serviceNode : controllerServiceNodes) { + final VersionedControllerService versionedControllerService = flowMapper.mapControllerService( + serviceNode, controllerServiceProvider, Collections.emptySet(), Collections.emptyMap()); + controllerServices.add(versionedControllerService); + } + + return controllerServices; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java index 8387d3e71c..4904fdef4d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java @@ -29,6 +29,7 @@ import org.apache.nifi.controller.service.ControllerServiceState; import org.apache.nifi.diagnostics.DiagnosticLevel; import org.apache.nifi.flow.ExternalControllerServiceReference; import org.apache.nifi.flow.ParameterProviderReference; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; import org.apache.nifi.flow.VersionedParameterContext; import org.apache.nifi.flow.VersionedProcessGroup; import org.apache.nifi.groups.ProcessGroup; @@ -317,6 +318,21 @@ public interface NiFiServiceFacade { */ ControllerConfigurationEntity getControllerConfiguration(); + /** + * Gets the snapshot for the given reporting task. + * + * @param reportingTaskId the id of the reporting task to get the snapshot for + * @return the reporting task snapshot + */ + VersionedReportingTaskSnapshot getVersionedReportingTaskSnapshot(String reportingTaskId); + + /** + * Gets the snapshot of all reporting tasks. + * + * @return the reporting task snapshot + */ + VersionedReportingTaskSnapshot getVersionedReportingTaskSnapshot(); + /** * Gets the controller level bulletins. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index a09aff5dce..eba0110932 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -91,7 +91,9 @@ import org.apache.nifi.controller.leader.election.LeaderElectionManager; import org.apache.nifi.controller.repository.FlowFileEvent; import org.apache.nifi.controller.repository.FlowFileEventRepository; import org.apache.nifi.controller.repository.claim.ContentDirection; +import org.apache.nifi.controller.serialization.VersionedReportingTaskSnapshotMapper; import org.apache.nifi.controller.service.ControllerServiceNode; +import org.apache.nifi.controller.service.ControllerServiceProvider; import org.apache.nifi.controller.service.ControllerServiceReference; import org.apache.nifi.controller.service.ControllerServiceState; import org.apache.nifi.controller.status.ProcessGroupStatus; @@ -113,6 +115,7 @@ import org.apache.nifi.flow.VersionedExternalFlowMetadata; import org.apache.nifi.flow.VersionedFlowCoordinates; import org.apache.nifi.flow.VersionedParameterContext; import org.apache.nifi.flow.VersionedProcessGroup; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.groups.ProcessGroupCounts; import org.apache.nifi.groups.RemoteProcessGroup; @@ -4150,6 +4153,52 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { return entityFactory.createControllerConfigurationEntity(dto, revision, permissions); } + @Override + public VersionedReportingTaskSnapshot getVersionedReportingTaskSnapshot(final String reportingTaskId) { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + final ReportingTaskNode reportingTaskNode = reportingTaskDAO.getReportingTask(reportingTaskId); + return getVersionedReportingTaskSnapshot(Collections.singleton(reportingTaskNode), user); + } + + @Override + public VersionedReportingTaskSnapshot getVersionedReportingTaskSnapshot() { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + final Set reportingTaskNodes = reportingTaskDAO.getReportingTasks(); + return getVersionedReportingTaskSnapshot(reportingTaskNodes, user); + } + + private VersionedReportingTaskSnapshot getVersionedReportingTaskSnapshot(final Set reportingTaskNodes, final NiFiUser user) { + final Set serviceNodes = new HashSet<>(); + reportingTaskNodes.forEach(reportingTaskNode -> { + reportingTaskNode.authorize(authorizer, RequestAction.READ, user); + findReferencedControllerServices(reportingTaskNode, serviceNodes, user); + }); + + final ExtensionManager extensionManager = controllerFacade.getExtensionManager(); + final ControllerServiceProvider serviceProvider = controllerFacade.getControllerServiceProvider(); + final VersionedReportingTaskSnapshotMapper snapshotMapper = new VersionedReportingTaskSnapshotMapper(extensionManager, serviceProvider); + return snapshotMapper.createMapping(reportingTaskNodes, serviceNodes); + } + + private void findReferencedControllerServices(final ComponentNode componentNode, final Set serviceNodes, final NiFiUser user) { + componentNode.getPropertyDescriptors().forEach(descriptor -> { + if (descriptor.getControllerServiceDefinition() != null) { + final String serviceId = componentNode.getEffectivePropertyValue(descriptor); + if (serviceId != null) { + try { + final ControllerServiceNode serviceNode = controllerServiceDAO.getControllerService(serviceId); + serviceNode.authorize(authorizer, RequestAction.READ, user); + if (serviceNodes.add(serviceNode)) { + findReferencedControllerServices(serviceNode, serviceNodes, user); + } + } catch (ResourceNotFoundException e) { + // ignore if the resource is not found, if the referenced service was previously deleted, it should not stop this action + } + } + } + }); + } + @Override public ControllerBulletinsEntity getControllerBulletins() { final NiFiUser user = NiFiUserUtils.getNiFiUser(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java index a0f3701a41..ddad726a14 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java @@ -26,38 +26,6 @@ import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; import io.swagger.annotations.SwaggerDefinition; import io.swagger.annotations.Tag; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.StreamingOutput; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.RequestAction; @@ -77,6 +45,7 @@ import org.apache.nifi.controller.ScheduledState; import org.apache.nifi.controller.service.ControllerServiceNode; import org.apache.nifi.controller.service.ControllerServiceState; import org.apache.nifi.flow.ExecutionEngine; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.nar.NarClassLoadersHolder; import org.apache.nifi.registry.client.NiFiRegistryException; @@ -159,6 +128,41 @@ import org.apache.nifi.web.api.request.FlowMetricsRegistry; import org.apache.nifi.web.api.request.IntegerParameter; import org.apache.nifi.web.api.request.LongParameter; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import java.text.Collator; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static org.apache.nifi.web.api.entity.ScheduleComponentsEntity.STATE_DISABLED; import static org.apache.nifi.web.api.entity.ScheduleComponentsEntity.STATE_ENABLED; @@ -178,6 +182,9 @@ public class FlowResource extends ApplicationResource { private static final String NIFI_REGISTRY_TYPE = "org.apache.nifi.registry.flow.NifiRegistryFlowRegistryClient"; private static final String RECURSIVE = "false"; + private static final String VERSIONED_REPORTING_TASK_SNAPSHOT_FILENAME_PATTERN = "VersionedReportingTaskSnapshot-%s.json"; + private static final String VERSIONED_REPORTING_TASK_SNAPSHOT_DATE_FORMAT = "yyyyMMddHHmmss"; + private NiFiServiceFacade serviceFacade; private Authorizer authorizer; @@ -680,6 +687,86 @@ public class FlowResource extends ApplicationResource { return generateOkResponse(entity).build(); } + /** + * Gets a snapshot of reporting tasks. + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.APPLICATION_JSON) + @Path("reporting-tasks/snapshot") + @ApiOperation( + value = "Get a snapshot of the given reporting tasks and any controller services they use", + response = VersionedReportingTaskSnapshot.class, + authorizations = { + @Authorization(value = "Read - /flow") + } + ) + @ApiResponses( + value = { + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") + } + ) + public Response getReportingTaskSnapshot( + @ApiParam(value = "Specifies a reporting task id to export. If not specified, all reporting tasks will be exported.") + @QueryParam("reportingTaskId") final String reportingTaskId + ) { + + authorizeFlow(); + + if (isReplicateRequest()) { + return replicate(HttpMethod.GET); + } + + final VersionedReportingTaskSnapshot snapshot = reportingTaskId == null + ? serviceFacade.getVersionedReportingTaskSnapshot() : + serviceFacade.getVersionedReportingTaskSnapshot(reportingTaskId); + + return generateOkResponse(snapshot).build(); + } + + /** + * Downloads a snapshot of reporting tasks. + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.APPLICATION_JSON) + @Path("reporting-tasks/download") + @ApiOperation( + value = "Download a snapshot of the given reporting tasks and any controller services they use", + response = byte[].class, + authorizations = { + @Authorization(value = "Read - /flow") + } + ) + @ApiResponses( + value = { + @ApiResponse(code = 401, message = "Client could not be authenticated."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.") + } + ) + public Response downloadReportingTaskSnapshot( + @ApiParam(value = "Specifies a reporting task id to export. If not specified, all reporting tasks will be exported.") + @QueryParam("reportingTaskId") final String reportingTaskId + ) { + + authorizeFlow(); + + if (isReplicateRequest()) { + return replicate(HttpMethod.GET); + } + + final VersionedReportingTaskSnapshot snapshot = reportingTaskId == null + ? serviceFacade.getVersionedReportingTaskSnapshot() : + serviceFacade.getVersionedReportingTaskSnapshot(reportingTaskId); + + final SimpleDateFormat dateFormat = new SimpleDateFormat(VERSIONED_REPORTING_TASK_SNAPSHOT_DATE_FORMAT); + final String filename = VERSIONED_REPORTING_TASK_SNAPSHOT_FILENAME_PATTERN.formatted(dateFormat.format(new Date())); + return generateOkResponse(snapshot).header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", filename)).build(); + } + /** * Updates the specified process group. * diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java index 2d1c41edb3..ef1231b15a 100644 --- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/FlowClient.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.toolkit.cli.impl.client.nifi; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity; import org.apache.nifi.web.api.entity.ClusteSummaryEntity; import org.apache.nifi.web.api.entity.ConnectionStatusEntity; @@ -122,6 +123,20 @@ public interface FlowClient { */ ReportingTasksEntity getReportingTasks() throws NiFiClientException, IOException; + /** + * Retrieves the snapshot of all reporting tasks and their respective controller services. + * + * @return the snapshot + */ + VersionedReportingTaskSnapshot getReportingTaskSnapshot() throws NiFiClientException, IOException; + + /** + * Retrieves the snapshot of the given reporting task and it's respective controller services. + * + * @return the snapshot + */ + VersionedReportingTaskSnapshot getReportingTaskSnapshot(String reportingTaskId) throws NiFiClientException, IOException; + /** * Retrieves the parameter providers. * diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java index 8c8ea36151..8065ed6cca 100644 --- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyFlowClient.java @@ -17,6 +17,7 @@ package org.apache.nifi.toolkit.cli.impl.client.nifi.impl; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; import org.apache.nifi.toolkit.cli.impl.client.nifi.FlowClient; import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException; import org.apache.nifi.toolkit.cli.impl.client.nifi.ProcessGroupBox; @@ -246,6 +247,23 @@ public class JerseyFlowClient extends AbstractJerseyClient implements FlowClient }); } + @Override + public VersionedReportingTaskSnapshot getReportingTaskSnapshot() throws NiFiClientException, IOException { + return executeAction("Error retrieving reporting tasks", () -> { + final WebTarget target = flowTarget.path("reporting-tasks/snapshot"); + return getRequestBuilder(target).get(VersionedReportingTaskSnapshot.class); + }); + } + + @Override + public VersionedReportingTaskSnapshot getReportingTaskSnapshot(final String reportingTaskId) throws NiFiClientException, IOException { + return executeAction("Error retrieving reporting task", () -> { + final WebTarget target = flowTarget.path("reporting-tasks/snapshot") + .queryParam("reportingTaskId", reportingTaskId); + return getRequestBuilder(target).get(VersionedReportingTaskSnapshot.class); + }); + } + @Override public ParameterProvidersEntity getParamProviders() throws NiFiClientException, IOException { return executeAction("Error retrieving parameter providers", () -> { diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java index 168043a8e6..29feb8073d 100644 --- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/NiFiCommandGroup.java @@ -29,6 +29,8 @@ import org.apache.nifi.toolkit.cli.impl.command.nifi.cs.GetControllerServices; import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.ClusterSummary; import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.CreateReportingTask; import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.CurrentUser; +import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.ExportReportingTask; +import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.ExportReportingTasks; import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetControllerConfiguration; import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetReportingTask; import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetReportingTasks; @@ -150,6 +152,8 @@ public class NiFiCommandGroup extends AbstractCommandGroup { commands.add(new DeleteReportingTask()); commands.add(new StartReportingTasks()); commands.add(new StopReportingTasks()); + commands.add(new ExportReportingTasks()); + commands.add(new ExportReportingTask()); commands.add(new ListUsers()); commands.add(new CreateUser()); commands.add(new ListUserGroups()); diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTask.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTask.java new file mode 100644 index 0000000000..331438ae5e --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTask.java @@ -0,0 +1,67 @@ +/* + * 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.nifi.toolkit.cli.impl.command.nifi.flow; + +import org.apache.commons.cli.MissingOptionException; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; +import org.apache.nifi.toolkit.cli.api.CommandException; +import org.apache.nifi.toolkit.cli.api.Context; +import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient; +import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException; +import org.apache.nifi.toolkit.cli.impl.command.CommandOption; +import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand; +import org.apache.nifi.toolkit.cli.impl.result.nifi.VersionedReportingTaskSnapshotResult; + +import java.io.IOException; +import java.util.Properties; + +public class ExportReportingTask extends AbstractNiFiCommand { + + public ExportReportingTask() { + super("export-reporting-task", VersionedReportingTaskSnapshotResult.class); + } + + @Override + public void doInitialize(final Context context) { + addOption(CommandOption.RT_ID.createOption()); + addOption(CommandOption.OUTPUT_FILE.createOption()); + } + + @Override + public String getDescription() { + return "Exports a snapshot of the specified reporting task and any management controller services used by the reporting task. " + + " The --" + CommandOption.OUTPUT_FILE.getLongName() + " can be used to export to a file, " + + "otherwise the content will be written to terminal or standard out."; + } + + @Override + public VersionedReportingTaskSnapshotResult doExecute(final NiFiClient client, final Properties properties) throws NiFiClientException, IOException, MissingOptionException, CommandException { + final String reportingTaskId = getRequiredArg(properties, CommandOption.RT_ID); + final VersionedReportingTaskSnapshot snapshot = client.getFlowClient().getReportingTaskSnapshot(reportingTaskId); + + // currently export doesn't use the ResultWriter concept, it always writes JSON + // destination will be a file if outputFile is specified, otherwise it will be the output stream of the CLI + final String outputFile; + if (properties.containsKey(CommandOption.OUTPUT_FILE.getLongName())) { + outputFile = properties.getProperty(CommandOption.OUTPUT_FILE.getLongName()); + } else { + outputFile = null; + } + + return new VersionedReportingTaskSnapshotResult(snapshot, outputFile); + } +} diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTasks.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTasks.java new file mode 100644 index 0000000000..7e5e5caffd --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/ExportReportingTasks.java @@ -0,0 +1,65 @@ +/* + * 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.nifi.toolkit.cli.impl.command.nifi.flow; + +import org.apache.commons.cli.MissingOptionException; +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; +import org.apache.nifi.toolkit.cli.api.CommandException; +import org.apache.nifi.toolkit.cli.api.Context; +import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient; +import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException; +import org.apache.nifi.toolkit.cli.impl.command.CommandOption; +import org.apache.nifi.toolkit.cli.impl.command.nifi.AbstractNiFiCommand; +import org.apache.nifi.toolkit.cli.impl.result.nifi.VersionedReportingTaskSnapshotResult; + +import java.io.IOException; +import java.util.Properties; + +public class ExportReportingTasks extends AbstractNiFiCommand { + + public ExportReportingTasks() { + super("export-reporting-tasks", VersionedReportingTaskSnapshotResult.class); + } + + @Override + public void doInitialize(final Context context) { + addOption(CommandOption.OUTPUT_FILE.createOption()); + } + + @Override + public String getDescription() { + return "Exports a snapshot of all reporting tasks and any management controller services used by the reporting tasks. " + + " The --" + CommandOption.OUTPUT_FILE.getLongName() + " can be used to export to a file, " + + "otherwise the content will be written to terminal or standard out."; + } + + @Override + public VersionedReportingTaskSnapshotResult doExecute(final NiFiClient client, final Properties properties) throws NiFiClientException, IOException, MissingOptionException, CommandException { + final VersionedReportingTaskSnapshot snapshot = client.getFlowClient().getReportingTaskSnapshot(); + + // currently export doesn't use the ResultWriter concept, it always writes JSON + // destination will be a file if outputFile is specified, otherwise it will be the output stream of the CLI + final String outputFile; + if (properties.containsKey(CommandOption.OUTPUT_FILE.getLongName())) { + outputFile = properties.getProperty(CommandOption.OUTPUT_FILE.getLongName()); + } else { + outputFile = null; + } + + return new VersionedReportingTaskSnapshotResult(snapshot, outputFile); + } +} diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/VersionedReportingTaskSnapshotResult.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/VersionedReportingTaskSnapshotResult.java new file mode 100644 index 0000000000..e8dc911253 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/VersionedReportingTaskSnapshotResult.java @@ -0,0 +1,63 @@ +/* + * 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.nifi.toolkit.cli.impl.result.nifi; + +import org.apache.nifi.flow.VersionedReportingTaskSnapshot; +import org.apache.nifi.toolkit.cli.api.WritableResult; +import org.apache.nifi.toolkit.cli.impl.util.JacksonUtils; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Objects; + +/** + * Result for a VersionedReportingTaskSnapshot. + * + * If this result was created with a non-null exportFileName, then the write method will ignore + * the passed in PrintStream, and will write the serialized snapshot to the give file. + * + * If this result was created with a null exportFileName, then the write method will write the + * serialized snapshot to the given PrintStream. + */ +public class VersionedReportingTaskSnapshotResult implements WritableResult { + + private final VersionedReportingTaskSnapshot versionedReportingTaskSnapshot; + private final String exportFileName; + + public VersionedReportingTaskSnapshotResult(final VersionedReportingTaskSnapshot versionedReportingTaskSnapshot, final String exportFileName) { + this.versionedReportingTaskSnapshot = Objects.requireNonNull(versionedReportingTaskSnapshot); + this.exportFileName = exportFileName; + } + + @Override + public VersionedReportingTaskSnapshot getResult() { + return versionedReportingTaskSnapshot; + } + + @Override + public void write(final PrintStream output) throws IOException { + if (exportFileName != null) { + try (final OutputStream resultOut = new FileOutputStream(exportFileName)) { + JacksonUtils.write(versionedReportingTaskSnapshot, resultOut); + } + } else { + JacksonUtils.write(versionedReportingTaskSnapshot, output); + } + } +}