This is an automated email from the ASF dual-hosted git repository.

markap14 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new ae61ebb5ed NIFI-12186 Add ability to export a versioned reporting task 
snapshot (#7853)
ae61ebb5ed is described below

commit ae61ebb5eda7076c6a18ba0419b614b8516faf14
Author: Bryan Bende <bbe...@apache.org>
AuthorDate: Mon Oct 9 10:50:36 2023 -0400

    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
---
 .../nifi/flow/VersionedReportingTaskSnapshot.java  |  48 +++++++
 .../flow/mapping/NiFiRegistryFlowMapper.java       |   3 +-
 .../VersionedReportingTaskSnapshotMapper.java      |  72 ++++++++++
 .../org/apache/nifi/web/NiFiServiceFacade.java     |  16 +++
 .../apache/nifi/web/StandardNiFiServiceFacade.java |  49 +++++++
 .../java/org/apache/nifi/web/api/FlowResource.java | 151 ++++++++++++++++-----
 .../toolkit/cli/impl/client/nifi/FlowClient.java   |  15 ++
 .../impl/client/nifi/impl/JerseyFlowClient.java    |  18 +++
 .../cli/impl/command/nifi/NiFiCommandGroup.java    |   4 +
 .../command/nifi/flow/ExportReportingTask.java     |  67 +++++++++
 .../command/nifi/flow/ExportReportingTasks.java    |  65 +++++++++
 .../nifi/VersionedReportingTaskSnapshotResult.java |  63 +++++++++
 12 files changed, 538 insertions(+), 33 deletions(-)

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<VersionedReportingTask> reportingTasks;
+    private List<VersionedControllerService> controllerServices;
+
+    @ApiModelProperty(value = "The controller services")
+    public List<VersionedControllerService> getControllerServices() {
+        return controllerServices;
+    }
+
+    public void setControllerServices(List<VersionedControllerService> 
controllerServices) {
+        this.controllerServices = controllerServices;
+    }
+
+    @ApiModelProperty(value = "The reporting tasks")
+    public List<VersionedReportingTask> getReportingTasks() {
+        return reportingTasks;
+    }
+
+    public void setReportingTasks(List<VersionedReportingTask> 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<ReportingTaskNode> reportingTaskNodes, final Set<ControllerServiceNode> 
controllerServiceNodes) {
+        final VersionedReportingTaskSnapshot versionedReportingTaskSnapshot = 
new VersionedReportingTaskSnapshot();
+        
versionedReportingTaskSnapshot.setReportingTasks(mapReportingTasks(reportingTaskNodes));
+        
versionedReportingTaskSnapshot.setControllerServices(mapControllerServices(controllerServiceNodes));
+        return versionedReportingTaskSnapshot;
+    }
+
+    private List<VersionedReportingTask> mapReportingTasks(final 
Set<ReportingTaskNode> reportingTaskNodes) {
+        final List<VersionedReportingTask> reportingTasks = new ArrayList<>();
+
+        for (final ReportingTaskNode taskNode : reportingTaskNodes) {
+            final VersionedReportingTask versionedReportingTask = 
flowMapper.mapReportingTask(taskNode, controllerServiceProvider);
+            reportingTasks.add(versionedReportingTask);
+        }
+
+        return reportingTasks;
+    }
+
+    private List<VersionedControllerService> mapControllerServices(final 
Set<ControllerServiceNode> controllerServiceNodes) {
+        final List<VersionedControllerService> 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<ReportingTaskNode> reportingTaskNodes = 
reportingTaskDAO.getReportingTasks();
+        return getVersionedReportingTaskSnapshot(reportingTaskNodes, user);
+    }
+
+    private VersionedReportingTaskSnapshot 
getVersionedReportingTaskSnapshot(final Set<ReportingTaskNode> 
reportingTaskNodes, final NiFiUser user) {
+        final Set<ControllerServiceNode> 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<ControllerServiceNode> 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<VersionedReportingTaskSnapshotResult> {
+
+    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<VersionedReportingTaskSnapshotResult> {
+
+    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<VersionedReportingTaskSnapshot> {
+
+    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);
+        }
+    }
+}


Reply via email to