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

turcsanyi 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 c6fbd86cd8 NIFI-13388 Add NiFi CLI support for Flow Analysis Rules
c6fbd86cd8 is described below

commit c6fbd86cd8e9a35b83c282da95a9d654e0b66fc8
Author: tpalfy <tpa...@apache.org>
AuthorDate: Tue Jun 11 16:58:20 2024 +0200

    NIFI-13388 Add NiFi CLI support for Flow Analysis Rules
    
    This closes #8954.
    
    Signed-off-by: Peter Turcsanyi <turcsa...@apache.org>
---
 nifi-docs/src/main/asciidoc/toolkit-guide.adoc     |  35 ++--
 ...SensitiveDynamicPropertiesFlowAnalysisRule.java |   1 -
 .../apache/nifi/tests/system/NiFiClientUtil.java   | 160 +++++++++++++++++
 .../org/apache/nifi/tests/system/NiFiSystemIT.java |   3 +
 .../flowanalysisrule/FlowAnalysisRuleIT.java       | 108 ++++++++++++
 ...lusteredVerifiableFlowAnalysisRuleSystemIT.java |  59 +++++++
 .../VerifiableFlowAnalysisRuleSystemIT.java        | 159 +++++++++++++++++
 .../cli/impl/client/nifi/ControllerClient.java     |  25 +++
 .../client/nifi/impl/JerseyControllerClient.java   | 192 ++++++++++++++++++++-
 .../toolkit/cli/impl/command/CommandOption.java    |   3 +
 .../cli/impl/command/nifi/NiFiCommandGroup.java    |  12 ++
 .../command/nifi/flow/CreateFlowAnalysisRule.java  |  79 +++++++++
 .../command/nifi/flow/DeleteFlowAnalysisRule.java  |  65 +++++++
 .../nifi/flow/DisableFlowAnalysisRules.java        |  98 +++++++++++
 .../command/nifi/flow/EnableFlowAnalysisRules.java |  98 +++++++++++
 .../command/nifi/flow/GetFlowAnalysisRule.java     |  62 +++++++
 .../command/nifi/flow/GetFlowAnalysisRules.java    |  52 ++++++
 .../impl/result/nifi/FlowAnalysisRuleResult.java   |  53 ++++++
 .../impl/result/nifi/FlowAnalysisRulesResult.java  |  88 ++++++++++
 19 files changed, 1332 insertions(+), 20 deletions(-)

diff --git a/nifi-docs/src/main/asciidoc/toolkit-guide.adoc 
b/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
index b6ef3ec565..70f0ea8286 100644
--- a/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
@@ -77,7 +77,6 @@ The following are available commands:
  nifi pg-start
  nifi pg-stop
  nifi pg-create
- nifi pg-export
  nifi pg-get-version
  nifi pg-stop-version-control
  nifi pg-change-version
@@ -92,6 +91,7 @@ The following are available commands:
  nifi pg-get-param-context
  nifi pg-set-param-context
  nifi pg-replace
+ nifi pg-export
  nifi get-services
  nifi get-service
  nifi create-service
@@ -106,6 +106,12 @@ The following are available commands:
  nifi export-reporting-tasks
  nifi export-reporting-task
  nifi import-reporting-tasks
+ nifi create-flow-analysis-rule
+ nifi get-flow-analysis-rules
+ nifi get-flow-analysis-rule
+ nifi enable-flow-analysis-rules
+ nifi disable-flow-analysis-rules
+ nifi delete-flow-analysis-rule
  nifi list-users
  nifi create-user
  nifi list-user-groups
@@ -296,21 +302,18 @@ For example, typing tab at an empty prompt should display 
possible commands for
 Typing "nifi " and then a tab will show the sub-commands for NiFi:
 
  #> nifi
- change-version-processor   delete-reporting-task           get-policy         
     merge-param-context     pg-replace                      update-user-group
- cluster-summary            disable-services                get-reg-client-id  
     offload-node            pg-set-param-context
- connect-node               disconnect-node                 get-reporting-task 
     pg-change-version       pg-start
- create-param-context       enable-services                 
get-reporting-tasks     pg-connect              pg-status
- create-param-provider      export-param-context            get-root-id        
     pg-create               pg-stop
- create-reg-client          export-reporting-task           get-service        
     pg-create-service       pg-stop-version-control
- create-reporting-task      export-reporting-tasks          get-services       
     pg-disable-services     remove-inherited-param-contexts
- create-service             fetch-params                    
import-param-context    pg-enable-services      set-inherited-param-contexts
- create-user                get-access-token                
import-reporting-tasks  pg-export               set-param
- create-user-group          get-access-token-spnego         
list-param-contexts     pg-get-all-versions     set-param-provider-property
- current-user               get-controller-configuration    
list-param-providers    pg-get-param-context    start-reporting-tasks
- delete-node                get-node                        list-reg-clients   
     pg-get-services         stop-reporting-tasks
- delete-param               get-nodes                       list-user-groups   
     pg-get-version          update-controller-configuration
- delete-param-context       get-param-context               list-users         
     pg-import               update-policy
- delete-param-provider      get-param-provider              
logout-access-token     pg-list                 update-reg-client
+ change-version-processor          delete-flow-analysis-rule         
export-reporting-task             get-policy                        
list-user-groups                  pg-export                         
pg-stop-version-control
+ cluster-summary                   delete-node                       
export-reporting-tasks            get-reg-client-id                 list-users  
                      pg-get-all-versions               
remove-inherited-param-contexts
+ connect-node                      delete-param                      
fetch-params                      get-reporting-task                
logout-access-token               pg-get-param-context              
set-inherited-param-contexts
+ create-flow-analysis-rule         delete-param-context              
get-access-token                  get-reporting-tasks               
merge-param-context               pg-get-services                   set-param
+ create-param-context              delete-param-provider             
get-access-token-spnego           get-root-id                       
offload-node                      pg-get-version                    
set-param-provider-property
+ create-param-provider             delete-reporting-task             
get-controller-configuration      get-service                       
pg-change-all-versions            pg-import                         
start-reporting-tasks
+ create-reg-client                 disable-flow-analysis-rules       
get-flow-analysis-rule            get-services                      
pg-change-version                 pg-list                           
stop-reporting-tasks
+ create-reporting-task             disable-services                  
get-flow-analysis-rules           import-param-context              pg-connect  
                      pg-replace                        
update-controller-configuration
+ create-service                    disconnect-node                   get-node  
                        import-reporting-tasks            pg-create             
            pg-set-param-context              update-policy
+ create-user                       enable-flow-analysis-rules        get-nodes 
                        list-param-contexts               pg-create-service     
            pg-start                          update-reg-client
+ create-user-group                 enable-services                   
get-param-context                 list-param-providers              
pg-disable-services               pg-status                         
update-user-group
+ current-user                      export-param-context              
get-param-provider                list-reg-clients                  
pg-enable-services                pg-stop
 
 Arguments that represent a path to a file, such as `-p` or when setting a 
properties file in the session, will auto-complete the path being typed:
 
diff --git 
a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/flowanalysis/SensitiveDynamicPropertiesFlowAnalysisRule.java
 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/flowanalysis/SensitiveDynamicPropertiesFlowAnalysisRule.java
index 7c6f73f2a7..b58e760cbd 100644
--- 
a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/flowanalysis/SensitiveDynamicPropertiesFlowAnalysisRule.java
+++ 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/flowanalysis/SensitiveDynamicPropertiesFlowAnalysisRule.java
@@ -28,7 +28,6 @@ public class SensitiveDynamicPropertiesFlowAnalysisRule 
extends AbstractFlowAnal
         return new PropertyDescriptor.Builder().name(propertyName)
                 .addValidator(Validator.VALID)
                 .dynamic(true)
-                .sensitive(true)
                 .build();
     }
 }
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
index cf2e190fee..8e62196f6e 100644
--- 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
@@ -39,6 +39,7 @@ import org.apache.nifi.web.api.dto.ControllerServiceDTO;
 import org.apache.nifi.web.api.dto.CounterDTO;
 import org.apache.nifi.web.api.dto.CountersSnapshotDTO;
 import org.apache.nifi.web.api.dto.DifferenceDTO;
+import org.apache.nifi.web.api.dto.FlowAnalysisRuleDTO;
 import org.apache.nifi.web.api.dto.FlowFileSummaryDTO;
 import org.apache.nifi.web.api.dto.FlowRegistryClientDTO;
 import org.apache.nifi.web.api.dto.FlowSnippetDTO;
@@ -76,6 +77,9 @@ import 
org.apache.nifi.web.api.entity.ControllerServicesEntity;
 import org.apache.nifi.web.api.entity.CopySnippetRequestEntity;
 import org.apache.nifi.web.api.entity.CountersEntity;
 import org.apache.nifi.web.api.entity.DropRequestEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleRunStatusEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
 import org.apache.nifi.web.api.entity.FlowComparisonEntity;
 import org.apache.nifi.web.api.entity.FlowEntity;
 import org.apache.nifi.web.api.entity.FlowFileEntity;
@@ -395,6 +399,141 @@ public class NiFiClientUtil {
         }
     }
 
+    public FlowAnalysisRuleEntity createFlowAnalysisRule(final String type) 
throws NiFiClientException, IOException {
+        return 
createFlowAnalysisRule(NiFiSystemIT.TEST_FLOW_ANALYSIS_RULE_PACKAGE + "." + 
type, getTestBundle());
+    }
+
+    public FlowAnalysisRuleEntity createFlowAnalysisRule(final String type, 
final BundleDTO bundle) throws NiFiClientException, IOException {
+        final FlowAnalysisRuleDTO dto = new FlowAnalysisRuleDTO();
+        dto.setBundle(bundle);
+        dto.setType(type);
+
+        final FlowAnalysisRuleEntity entity = new FlowAnalysisRuleEntity();
+        entity.setComponent(dto);
+        entity.setRevision(createNewRevision());
+        entity.setDisconnectedNodeAcknowledged(true);
+
+        final FlowAnalysisRuleEntity flowAnalysisRule = 
nifiClient.getControllerClient().createFlowAnalysisRule(entity);
+        logger.info("Created Flow Analysis Rule [type={}, id={}] for Test 
[{}]", simpleName(type), flowAnalysisRule.getId(), testName);
+
+        return flowAnalysisRule;
+    }
+
+    public FlowAnalysisRuleEntity updateFlowAnalysisRuleProperties(final 
FlowAnalysisRuleEntity currentEntity, final Map<String, String> properties) 
throws NiFiClientException, IOException {
+        final FlowAnalysisRuleDTO ruleDto = new FlowAnalysisRuleDTO();
+        ruleDto.setProperties(properties);
+        ruleDto.setId(currentEntity.getId());
+
+        final FlowAnalysisRuleEntity updatedEntity = new 
FlowAnalysisRuleEntity();
+        updatedEntity.setRevision(currentEntity.getRevision());
+        updatedEntity.setComponent(ruleDto);
+        updatedEntity.setId(currentEntity.getId());
+        updatedEntity.setDisconnectedNodeAcknowledged(true);
+
+        return 
nifiClient.getControllerClient().updateFlowAnalysisRule(updatedEntity);
+
+    }
+
+    public FlowAnalysisRuleEntity enableFlowAnalysisRule(final 
FlowAnalysisRuleEntity entity) throws NiFiClientException, IOException {
+        final FlowAnalysisRuleRunStatusEntity runStatusEntity = new 
FlowAnalysisRuleRunStatusEntity();
+        runStatusEntity.setState("ENABLED");
+        runStatusEntity.setRevision(entity.getRevision());
+        runStatusEntity.setDisconnectedNodeAcknowledged(true);
+
+        return 
nifiClient.getControllerClient().activateFlowAnalysisRule(entity.getId(), 
runStatusEntity);
+    }
+
+    public FlowAnalysisRuleEntity disableFlowAnalysisRule(final 
FlowAnalysisRuleEntity entity) throws NiFiClientException, IOException {
+        final FlowAnalysisRuleRunStatusEntity runStatusEntity = new 
FlowAnalysisRuleRunStatusEntity();
+        runStatusEntity.setState("DISABLED");
+        runStatusEntity.setRevision(entity.getRevision());
+        runStatusEntity.setDisconnectedNodeAcknowledged(true);
+
+        return 
nifiClient.getControllerClient().activateFlowAnalysisRule(entity.getId(), 
runStatusEntity);
+    }
+
+    public void disableFlowAnalysisRules() throws NiFiClientException, 
IOException {
+        final FlowAnalysisRulesEntity rules = 
nifiClient.getControllerClient().getFlowAnalysisRules();
+
+        Collection<String> toBeDisabledRuleIds = new ArrayList<>();
+        for (final FlowAnalysisRuleEntity rule : rules.getFlowAnalysisRules()) 
{
+            disableFlowAnalysisRule(rule);
+            toBeDisabledRuleIds.add(rule.getId());
+        }
+
+        waitForFlowAnalysisRuleState("DISABLED", toBeDisabledRuleIds);
+    }
+
+    public void deleteFlowAnalysisRules() throws NiFiClientException, 
IOException {
+        final FlowAnalysisRulesEntity rulesEntity = 
nifiClient.getControllerClient().getFlowAnalysisRules();
+        for (final FlowAnalysisRuleEntity taskEntity : 
rulesEntity.getFlowAnalysisRules()) {
+            taskEntity.setDisconnectedNodeAcknowledged(true);
+            
nifiClient.getControllerClient().deleteFlowAnalysisRule(taskEntity);
+        }
+    }
+
+    public void waitForFlowAnalysisRuleState(final String desiredState, final 
Collection<String> ruleIdsOfInterest) throws NiFiClientException, IOException {
+        final long maxTimestamp = System.currentTimeMillis() + 
TimeUnit.MINUTES.toMillis(2L);
+
+        while (System.currentTimeMillis() < maxTimestamp) {
+            final List<FlowAnalysisRuleEntity> flowAnalysisRulesNotInState = 
getFlowAnalysisRulesNotInState(desiredState, ruleIdsOfInterest);
+            if (flowAnalysisRulesNotInState.isEmpty()) {
+                logger.info("Flow Analysis Rules have desired state [{}]", 
desiredState);
+                return;
+            }
+
+            final FlowAnalysisRuleEntity entity = 
flowAnalysisRulesNotInState.get(0);
+            logger.info(
+                    "Flow Analysis Rule ID [{}] Type [{}] State [{}] waiting 
for State [{}]: sleeping for 500 ms before retrying",
+                    entity.getId(), entity.getComponent().getType(), 
entity.getComponent().getState(), desiredState
+            );
+
+            try {
+                Thread.sleep(500L);
+            } catch (final Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public List<FlowAnalysisRuleEntity> getFlowAnalysisRulesNotInState(final 
String desiredState, final Collection<String> ruleIds) throws 
NiFiClientException, IOException {
+        final FlowAnalysisRulesEntity rulesEntity = 
nifiClient.getControllerClient().getFlowAnalysisRules();
+
+        return rulesEntity.getFlowAnalysisRules().stream()
+                .filter(rule -> ruleIds == null || ruleIds.isEmpty() || 
ruleIds.contains(rule.getId()))
+                .filter(rule -> 
!desiredState.equalsIgnoreCase(rule.getComponent().getState()))
+                .collect(Collectors.toList());
+    }
+
+    public void waitForFlowAnalysisRuleValid(final String reportingTaskId) 
throws NiFiClientException, IOException {
+        waitForFlowAnalysisRuleValidationStatus(reportingTaskId, "Valid");
+    }
+
+    public void waitForFlowAnalysisRuleValidationStatus(final String 
flowAnalysisRuleId, final String validationStatus) throws NiFiClientException, 
IOException {
+        final long maxTimestamp = System.currentTimeMillis() + 
TimeUnit.MINUTES.toMillis(2L);
+
+        while (System.currentTimeMillis() < maxTimestamp) {
+            final FlowAnalysisRuleEntity flowAnalysisRuleEntity = 
nifiClient.getControllerClient().getFlowAnalysisRule(flowAnalysisRuleId);
+            final String currentValidationStatus = 
flowAnalysisRuleEntity.getStatus().getValidationStatus();
+            if (validationStatus.equalsIgnoreCase(currentValidationStatus)) {
+                logger.info("Flow Analysis Rule ID [{}] Type [{}] Validation 
Status [{}] matched",
+                        flowAnalysisRuleId, 
flowAnalysisRuleEntity.getComponent().getType(), validationStatus
+                );
+                return;
+            }
+
+            logger.info("Flow Analysis Rule ID [{}] Type [{}] Validation 
Status [{}] waiting for [{}]: sleeping for 500 ms before retrying",
+                    flowAnalysisRuleEntity, 
flowAnalysisRuleEntity.getComponent().getType(), currentValidationStatus, 
validationStatus
+            );
+
+            try {
+                Thread.sleep(500L);
+            } catch (final Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
     public ParameterEntity createParameterEntity(final String name, final 
String description, final boolean sensitive, final String value) {
         final ParameterDTO dto = new ParameterDTO();
         dto.setName(name);
@@ -1678,6 +1817,27 @@ public class NiFiClientUtil {
         return results.getRequest().getResults();
     }
 
+    public List<ConfigVerificationResultDTO> 
verifyFlowAnalysisRuleConfig(final String ruleId, final Map<String, String> 
properties)
+            throws InterruptedException, IOException, NiFiClientException {
+
+        final VerifyConfigRequestDTO requestDto = new VerifyConfigRequestDTO();
+        requestDto.setComponentId(ruleId);
+        requestDto.setProperties(properties);
+
+        final VerifyConfigRequestEntity verificationRequest = new 
VerifyConfigRequestEntity();
+        verificationRequest.setRequest(requestDto);
+
+        VerifyConfigRequestEntity results = 
nifiClient.getControllerClient().submitFlowAnalysisRuleConfigVerificationRequest(verificationRequest);
+        while ((!results.getRequest().isComplete()) || 
(results.getRequest().getResults() == null)) {
+            Thread.sleep(50L);
+            results = 
nifiClient.getControllerClient().getFlowAnalysisRuleConfigVerificationRequest(ruleId,
 results.getRequest().getRequestId());
+        }
+
+        
nifiClient.getControllerClient().deleteFlowAnalysisRuleConfigVerificationRequest(ruleId,
 results.getRequest().getRequestId());
+
+        return results.getRequest().getResults();
+    }
+
 
     public ReportingTaskEntity createReportingTask(final String type, final 
String bundleGroupId, final String artifactId, final String version)
                 throws NiFiClientException, IOException {
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
index e816c2d000..57e2dbe0c6 100644
--- 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
@@ -88,6 +88,7 @@ public abstract class NiFiSystemIT implements 
NiFiInstanceProvider {
     public static final String TEST_PROCESSORS_PACKAGE = 
"org.apache.nifi.processors.tests.system";
     public static final String TEST_CS_PACKAGE = 
"org.apache.nifi.cs.tests.system";
     public static final String TEST_REPORTING_TASK_PACKAGE = 
"org.apache.nifi.reporting";
+    public static final String TEST_FLOW_ANALYSIS_RULE_PACKAGE = 
"org.apache.nifi.flowanalysis";
 
     private static final Pattern FRAMEWORK_NAR_PATTERN = 
Pattern.compile("nifi-framework-nar-(.*?)\\.nar");
     private static final File LIB_DIR = new 
File("target/nifi-lib-assembly/lib");
@@ -242,10 +243,12 @@ public abstract class NiFiSystemIT implements 
NiFiInstanceProvider {
         getClientUtil().disableControllerServices("root", true);
         getClientUtil().stopReportingTasks();
         getClientUtil().disableControllerLevelServices();
+        getClientUtil().disableFlowAnalysisRules();
         getClientUtil().stopTransmitting("root");
         getClientUtil().deleteAll("root");
         getClientUtil().deleteControllerLevelServices();
         getClientUtil().deleteReportingTasks();
+        getClientUtil().deleteFlowAnalysisRules();
 
         logger.info("Finished destroyFlow");
     }
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/flowanalysisrule/FlowAnalysisRuleIT.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/flowanalysisrule/FlowAnalysisRuleIT.java
new file mode 100644
index 0000000000..21b7a80a16
--- /dev/null
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/flowanalysisrule/FlowAnalysisRuleIT.java
@@ -0,0 +1,108 @@
+/*
+ * 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.tests.system.flowanalysisrule;
+
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.FlowAnalysisRuleDTO;
+import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class FlowAnalysisRuleIT extends NiFiSystemIT {
+
+
+    public static final String SENSITIVE_PROPERTY_NAME = "SensitiveProperty";
+
+    private static final String SENSITIVE_PROPERTY_VALUE = "SensitiveValue";
+
+    private static final Set<String> SENSITIVE_DYNAMIC_PROPERTY_NAMES = 
Collections.singleton(SENSITIVE_PROPERTY_NAME);
+
+    @Test
+    public void testGetPropertyDescriptor() throws NiFiClientException, 
IOException {
+        final FlowAnalysisRuleEntity flowAnalysisRuleEntity = 
getClientUtil().createFlowAnalysisRule("SensitiveDynamicPropertiesFlowAnalysisRule");
+
+        final PropertyDescriptorEntity propertyDescriptorEntity = 
getNifiClient().getControllerClient().getFlowAnalysisRulePropertyDescriptor(
+                flowAnalysisRuleEntity.getId(),
+                SENSITIVE_PROPERTY_NAME,
+                null
+        );
+        final PropertyDescriptorDTO propertyDescriptor = 
propertyDescriptorEntity.getPropertyDescriptor();
+        assertFalse(propertyDescriptor.isSensitive());
+        assertTrue(propertyDescriptor.isDynamic());
+
+        final PropertyDescriptorEntity sensitivePropertyDescriptorEntity = 
getNifiClient().getControllerClient().getFlowAnalysisRulePropertyDescriptor(
+                flowAnalysisRuleEntity.getId(),
+                SENSITIVE_PROPERTY_NAME,
+                true
+        );
+        final PropertyDescriptorDTO sensitivePropertyDescriptor = 
sensitivePropertyDescriptorEntity.getPropertyDescriptor();
+        assertTrue(sensitivePropertyDescriptor.isSensitive());
+        assertTrue(sensitivePropertyDescriptor.isDynamic());
+    }
+
+    @Test
+    public void testSensitiveDynamicPropertiesNotSupported() throws 
NiFiClientException, IOException {
+        final FlowAnalysisRuleEntity flowAnalysisRuleEntity = 
getClientUtil().createFlowAnalysisRule("ControllerServiceReferencingFlowAnalysisRule");
+        final FlowAnalysisRuleDTO component = 
flowAnalysisRuleEntity.getComponent();
+        assertFalse(component.getSupportsSensitiveDynamicProperties());
+
+        
component.setSensitiveDynamicPropertyNames(SENSITIVE_DYNAMIC_PROPERTY_NAMES);
+
+        
getNifiClient().getControllerClient().updateFlowAnalysisRule(flowAnalysisRuleEntity);
+
+        
getClientUtil().waitForFlowAnalysisRuleValidationStatus(flowAnalysisRuleEntity.getId(),
 FlowAnalysisRuleDTO.INVALID);
+    }
+
+    @Test
+    public void testSensitiveDynamicPropertiesSupportedConfigured() throws 
NiFiClientException, IOException {
+        final FlowAnalysisRuleEntity flowAnalysisRuleEntity = 
getClientUtil().createFlowAnalysisRule("SensitiveDynamicPropertiesFlowAnalysisRule");
+        final FlowAnalysisRuleDTO component = 
flowAnalysisRuleEntity.getComponent();
+        assertTrue(component.getSupportsSensitiveDynamicProperties());
+
+        
component.setSensitiveDynamicPropertyNames(SENSITIVE_DYNAMIC_PROPERTY_NAMES);
+        
component.setProperties(Collections.singletonMap(SENSITIVE_PROPERTY_NAME, 
SENSITIVE_PROPERTY_VALUE));
+
+        
getNifiClient().getControllerClient().updateFlowAnalysisRule(flowAnalysisRuleEntity);
+
+        final FlowAnalysisRuleEntity updatedFlowAnalysisRuleEntity = 
getNifiClient().getControllerClient().getFlowAnalysisRule(flowAnalysisRuleEntity.getId());
+        final FlowAnalysisRuleDTO updatedComponent = 
updatedFlowAnalysisRuleEntity.getComponent();
+
+        final Map<String, String> properties = 
updatedComponent.getProperties();
+        assertNotSame(SENSITIVE_PROPERTY_VALUE, 
properties.get(SENSITIVE_PROPERTY_NAME));
+
+        final Map<String, PropertyDescriptorDTO> descriptors = 
updatedComponent.getDescriptors();
+        final PropertyDescriptorDTO descriptor = 
descriptors.get(SENSITIVE_PROPERTY_NAME);
+        assertNotNull(descriptor);
+        assertTrue(descriptor.isSensitive());
+        assertTrue(descriptor.isDynamic());
+
+        
getClientUtil().waitForFlowAnalysisRuleValid(flowAnalysisRuleEntity.getId());
+    }
+}
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableFlowAnalysisRuleSystemIT.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableFlowAnalysisRuleSystemIT.java
new file mode 100644
index 0000000000..b79b58526b
--- /dev/null
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableFlowAnalysisRuleSystemIT.java
@@ -0,0 +1,59 @@
+/*
+ * 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.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ClusteredVerifiableFlowAnalysisRuleSystemIT extends 
VerifiableFlowAnalysisRuleSystemIT {
+    @Override
+    public NiFiInstanceFactory getInstanceFactory() {
+        return createTwoNodeInstanceFactory();
+    }
+
+    @Test
+    public void testDifferentResultsFromDifferentNodes() throws 
InterruptedException, IOException, NiFiClientException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("Successful Verification", "true");
+        properties.put("Failure Node Number", "2");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, properties);
+
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), properties);
+        assertEquals(3, resultList.size());
+
+        // First verification result will be component validation.
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(0).getOutcome());
+        // Second verification result will be verification results
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(1).getOutcome());
+        // Third verification result is for Fail On Primary Node
+        // assertEquals(Outcome.FAILED.name(), 
resultList.get(2).getOutcome());  // NIFI-9717
+    }
+}
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableFlowAnalysisRuleSystemIT.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableFlowAnalysisRuleSystemIT.java
new file mode 100644
index 0000000000..74f08f84fb
--- /dev/null
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableFlowAnalysisRuleSystemIT.java
@@ -0,0 +1,159 @@
+/*
+ * 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.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class VerifiableFlowAnalysisRuleSystemIT extends NiFiSystemIT {
+
+    @Test
+    public void testVerificationWithValidConfigWhenComponentValid() throws 
NiFiClientException, IOException, InterruptedException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> properties = 
Collections.singletonMap("Successful Verification", "true");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, properties);
+
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), properties);
+        assertEquals(3, resultList.size());
+
+        // First verification result will be component validation.
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(0).getOutcome());
+        // Second verification result will be verification results
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(1).getOutcome());
+        // Third verification result is for Fail On Primary Node
+        assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+    }
+
+
+    @Test
+    public void testVerifyWithInvalidConfigWhenComponentValid() throws 
NiFiClientException, IOException, InterruptedException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> properties = 
Collections.singletonMap("Successful Verification", "true");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, properties);
+
+        // Verify with properties that will give us failed verification
+        final Map<String, String> invalidProperties = 
Collections.singletonMap("Successful Verification", "false");
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), invalidProperties);
+        assertEquals(3, resultList.size());
+
+        // First verification result will be component validation.
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(0).getOutcome());
+        // Second verification result will be FAILED because the 'Successful 
Verification' property is set to false
+        assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+        // Third verification result is for Fail On Primary Node
+        assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+    }
+
+    @Test
+    public void testVerificationWithValidConfigWhenComponentInvalid() throws 
NiFiClientException, IOException, InterruptedException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> invalidProperties = 
Collections.singletonMap("Successful Verification", "foo");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, 
invalidProperties);
+
+        final Map<String, String> validProperties = 
Collections.singletonMap("Successful Verification", "true");
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), validProperties);
+        assertEquals(3, resultList.size());
+
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(0).getOutcome());
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(1).getOutcome());
+        assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+    }
+
+    @Test
+    public void testVerifyWithInvalidConfigWhenComponentInvalid() throws 
InterruptedException, IOException, NiFiClientException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> invalidProperties = 
Collections.singletonMap("Successful Verification", "foo");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, 
invalidProperties);
+
+        final Map<String, String> otherInvalidProperties = 
Collections.singletonMap("Successful Verification", "bar");
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), 
otherInvalidProperties);
+        assertEquals(1, resultList.size());
+
+        for (final ConfigVerificationResultDTO resultDto : resultList) {
+            assertEquals(Outcome.FAILED.name(), resultDto.getOutcome());
+        }
+    }
+
+    @Test
+    public void testVerificationWithValidConfigWhenComponentRunning() throws 
IOException, NiFiClientException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> properties = 
Collections.singletonMap("Successful Verification", "true");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, properties);
+
+        getClientUtil().enableFlowAnalysisRule(rule);
+
+        assertThrows(NiFiClientException.class, () -> 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), properties));
+    }
+
+
+    @Test
+    public void testVerifyWhenExceptionThrown() throws InterruptedException, 
IOException, NiFiClientException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("EnsureFlowAnalysisRuleConfigurationCorrect");
+
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("Successful Verification", "true");
+        properties.put("Exception on Verification", "true");
+        getClientUtil().updateFlowAnalysisRuleProperties(rule, properties);
+
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), properties);
+        assertEquals(2, resultList.size());
+
+        // Results should show that validation is successful but that there 
was a failure in performing verification
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(0).getOutcome());
+        assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+    }
+
+    @Test
+    public void 
testValidProcessorWithoutVerifiableFlowAnalysisRuleAnnotation() throws 
NiFiClientException, IOException, InterruptedException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("SensitiveDynamicPropertiesFlowAnalysisRule");
+
+        // Even though rule does not implement VerifiableFlowAnalysisRule, 
validation should still be run
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), 
Collections.emptyMap());
+        assertEquals(1, resultList.size());
+
+        assertEquals(Outcome.SUCCESSFUL.name(), 
resultList.get(0).getOutcome());
+    }
+
+    @Test
+    public void 
testInvalidConfigForRuleWithoutVerifiableFlowAnalysisRuleAnnotation() throws 
NiFiClientException, IOException, InterruptedException {
+        final FlowAnalysisRuleEntity rule = 
getClientUtil().createFlowAnalysisRule("ControllerServiceReferencingFlowAnalysisRule");
+
+        final List<ConfigVerificationResultDTO> resultList = 
getClientUtil().verifyFlowAnalysisRuleConfig(rule.getId(), 
Collections.emptyMap());
+        assertEquals(1, resultList.size());
+
+        assertEquals(Outcome.FAILED.name(), resultList.get(0).getOutcome());
+    }
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
index 34b3cdce29..2d002abad4 100644
--- 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerClient.java
@@ -19,11 +19,16 @@ package org.apache.nifi.toolkit.cli.impl.client.nifi;
 import org.apache.nifi.web.api.entity.ClusterEntity;
 import org.apache.nifi.web.api.entity.ControllerConfigurationEntity;
 import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleRunStatusEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
 import org.apache.nifi.web.api.entity.FlowRegistryClientEntity;
 import org.apache.nifi.web.api.entity.FlowRegistryClientsEntity;
 import org.apache.nifi.web.api.entity.NodeEntity;
 import org.apache.nifi.web.api.entity.ParameterProviderEntity;
+import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.VerifyConfigRequestEntity;
 import 
org.apache.nifi.web.api.entity.VersionedReportingTaskImportRequestEntity;
 import 
org.apache.nifi.web.api.entity.VersionedReportingTaskImportResponseEntity;
 
@@ -61,6 +66,26 @@ public interface ControllerClient {
     VersionedReportingTaskImportResponseEntity 
importReportingTasks(VersionedReportingTaskImportRequestEntity 
importRequestEntity)
             throws NiFiClientException, IOException;
 
+    FlowAnalysisRulesEntity getFlowAnalysisRules() throws NiFiClientException, 
IOException;
+
+    FlowAnalysisRuleEntity getFlowAnalysisRule(final String id) throws 
NiFiClientException, IOException;
+
+    PropertyDescriptorEntity getFlowAnalysisRulePropertyDescriptor(final 
String componentId, final String propertyName, final Boolean sensitive) throws 
NiFiClientException, IOException;
+
+    FlowAnalysisRuleEntity createFlowAnalysisRule(FlowAnalysisRuleEntity 
reportingTask) throws NiFiClientException, IOException;
+
+    FlowAnalysisRuleEntity updateFlowAnalysisRule(final FlowAnalysisRuleEntity 
flowAnalysisRuleEntity) throws NiFiClientException, IOException;
+
+    FlowAnalysisRuleEntity activateFlowAnalysisRule(final String id, final 
FlowAnalysisRuleRunStatusEntity runStatusEntity) throws NiFiClientException, 
IOException;
+
+    FlowAnalysisRuleEntity deleteFlowAnalysisRule(final FlowAnalysisRuleEntity 
flowAnalysisRule) throws NiFiClientException, IOException;
+
+    VerifyConfigRequestEntity 
submitFlowAnalysisRuleConfigVerificationRequest(final VerifyConfigRequestEntity 
configRequestEntity) throws NiFiClientException, IOException;
+
+    VerifyConfigRequestEntity 
getFlowAnalysisRuleConfigVerificationRequest(final String taskId, final String 
verificationRequestId) throws NiFiClientException, IOException;
+
+    VerifyConfigRequestEntity 
deleteFlowAnalysisRuleConfigVerificationRequest(final String taskId, final 
String verificationRequestId) throws NiFiClientException, IOException;
+
     ParameterProviderEntity createParamProvider(ParameterProviderEntity 
paramProvider) throws NiFiClientException, IOException;
 
     ControllerConfigurationEntity getControllerConfiguration() throws 
NiFiClientException, IOException;
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
index f621f4e6ed..e3e4b086ab 100644
--- 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerClient.java
@@ -20,21 +20,29 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
 import org.apache.nifi.toolkit.cli.impl.client.nifi.RequestConfig;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
 import org.apache.nifi.web.api.entity.ClusterEntity;
 import org.apache.nifi.web.api.entity.ControllerConfigurationEntity;
 import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleRunStatusEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
 import org.apache.nifi.web.api.entity.FlowRegistryClientEntity;
 import org.apache.nifi.web.api.entity.FlowRegistryClientsEntity;
 import org.apache.nifi.web.api.entity.NodeEntity;
 import org.apache.nifi.web.api.entity.ParameterProviderEntity;
+import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.VerifyConfigRequestEntity;
 import 
org.apache.nifi.web.api.entity.VersionedReportingTaskImportRequestEntity;
 import 
org.apache.nifi.web.api.entity.VersionedReportingTaskImportResponseEntity;
 
-import jakarta.ws.rs.client.Entity;
-import jakarta.ws.rs.client.WebTarget;
-import jakarta.ws.rs.core.MediaType;
 import java.io.IOException;
+import java.util.Objects;
 
 /**
  * Jersey implementation of ControllerClient.
@@ -246,6 +254,184 @@ public class JerseyControllerClient extends 
AbstractJerseyClient implements Cont
         });
     }
 
+    @Override
+    public FlowAnalysisRulesEntity getFlowAnalysisRules() throws 
NiFiClientException, IOException {
+        return executeAction("Error retrieving flow analysis rules", () -> {
+            final WebTarget target = 
controllerTarget.path("flow-analysis-rules");
+            return 
getRequestBuilder(target).get(FlowAnalysisRulesEntity.class);
+        });
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity getFlowAnalysisRule(final String id) throws 
NiFiClientException, IOException {
+        if (StringUtils.isBlank(id)) {
+            throw new IllegalArgumentException("Flow analysis rule id cannot 
be null");
+        }
+
+        return executeAction("Error retrieving status of flow analysis rule", 
() -> {
+            final WebTarget target = 
controllerTarget.path("flow-analysis-rules/{id}").resolveTemplate("id", id);
+            return getRequestBuilder(target).get(FlowAnalysisRuleEntity.class);
+        });
+    }
+
+    @Override
+    public PropertyDescriptorEntity 
getFlowAnalysisRulePropertyDescriptor(final String componentId, final String 
propertyName, final Boolean sensitive) throws NiFiClientException, IOException {
+        Objects.requireNonNull(componentId, "Component ID required");
+        Objects.requireNonNull(propertyName, "Property Name required");
+
+        return executeAction("Error retrieving Flow Analysis Rule Property 
Descriptor", () -> {
+            final WebTarget target = controllerTarget
+                    
.path("flow-analysis-rules/{id}/descriptors").resolveTemplate("id", componentId)
+                    .queryParam("propertyName", propertyName)
+                    .queryParam("sensitive", sensitive);
+
+            return 
getRequestBuilder(target).get(PropertyDescriptorEntity.class);
+        });
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity 
createFlowAnalysisRule(FlowAnalysisRuleEntity flowAnalysisRule) throws 
NiFiClientException, IOException {
+        if (flowAnalysisRule == null) {
+            throw new IllegalArgumentException("Flow analysis rule entity 
cannot be null");
+        }
+
+        return executeAction("Error creating flow analysis rule", () -> {
+            final WebTarget target = 
controllerTarget.path("flow-analysis-rules");
+
+            return getRequestBuilder(target).post(
+                    Entity.entity(flowAnalysisRule, 
MediaType.APPLICATION_JSON),
+                    FlowAnalysisRuleEntity.class
+            );
+        });
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity updateFlowAnalysisRule(final 
FlowAnalysisRuleEntity flowAnalysisRuleEntity) throws NiFiClientException, 
IOException {
+        if (flowAnalysisRuleEntity == null) {
+            throw new IllegalArgumentException("Flow Analysis Rule cannot be 
null");
+        }
+        if (flowAnalysisRuleEntity.getComponent() == null) {
+            throw new IllegalArgumentException("Component cannot be null");
+        }
+
+        return executeAction("Error updating Flow Analysis Rule", () -> {
+            final WebTarget target = 
controllerTarget.path("flow-analysis-rules/{id}").resolveTemplate("id", 
flowAnalysisRuleEntity.getId());
+            return getRequestBuilder(target).put(
+                    Entity.entity(flowAnalysisRuleEntity, 
MediaType.APPLICATION_JSON_TYPE),
+                    FlowAnalysisRuleEntity.class);
+        });
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity activateFlowAnalysisRule(
+            final String id,
+            final FlowAnalysisRuleRunStatusEntity runStatusEntity
+    ) throws NiFiClientException, IOException {
+        if (StringUtils.isBlank(id)) {
+            throw new IllegalArgumentException("Flow analysis rule id cannot 
be null");
+        }
+
+        if (runStatusEntity == null) {
+            throw new IllegalArgumentException("Entity cannot be null");
+        }
+
+        return executeAction("Error enabling or disabling flow analysis rule", 
() -> {
+            final WebTarget target = controllerTarget
+                    
.path("flow-analysis-rules/{id}/run-status").resolveTemplate("id", id);
+            return getRequestBuilder(target).put(
+                    Entity.entity(runStatusEntity, 
MediaType.APPLICATION_JSON_TYPE),
+                    FlowAnalysisRuleEntity.class
+            );
+        });
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity deleteFlowAnalysisRule(final 
FlowAnalysisRuleEntity flowAnalysisRule) throws NiFiClientException, 
IOException {
+        if (flowAnalysisRule == null) {
+            throw new IllegalArgumentException("Flow Analysis Rule Entity 
cannot be null");
+        }
+        if (flowAnalysisRule.getId() == null) {
+            throw new IllegalArgumentException("Flow Analysis Rule ID cannot 
be null");
+        }
+
+        final RevisionDTO revision = flowAnalysisRule.getRevision();
+        if (revision == null) {
+            throw new IllegalArgumentException("Revision cannot be null");
+        }
+
+        return executeAction("Error deleting Flow Analysis Rule", () -> {
+            WebTarget target = controllerTarget
+                    .path("flow-analysis-rules/{id}").resolveTemplate("id", 
flowAnalysisRule.getId())
+                    .queryParam("version", revision.getVersion())
+                    .queryParam("clientId", revision.getClientId());
+
+            if (flowAnalysisRule.isDisconnectedNodeAcknowledged() == 
Boolean.TRUE) {
+                target = target.queryParam("disconnectedNodeAcknowledged", 
"true");
+            }
+
+            return 
getRequestBuilder(target).delete(FlowAnalysisRuleEntity.class);
+        });
+    }
+
+    @Override
+    public VerifyConfigRequestEntity 
submitFlowAnalysisRuleConfigVerificationRequest(final VerifyConfigRequestEntity 
configRequestEntity) throws NiFiClientException, IOException {
+        if (configRequestEntity == null) {
+            throw new IllegalArgumentException("Config Request Entity cannot 
be null");
+        }
+        if (configRequestEntity.getRequest() == null) {
+            throw new IllegalArgumentException("Config Request DTO cannot be 
null");
+        }
+        if (configRequestEntity.getRequest().getComponentId() == null) {
+            throw new IllegalArgumentException("Flow Analysis Rule ID cannot 
be null");
+        }
+        if (configRequestEntity.getRequest().getProperties() == null) {
+            throw new IllegalArgumentException("Flow Analysis Rule properties 
cannot be null");
+        }
+
+        return executeAction("Error submitting Flow Analysis Rule Config 
Verification Request", () -> {
+            final WebTarget target = controllerTarget
+                    
.path("flow-analysis-rules/{id}/config/verification-requests")
+                    .resolveTemplate("id", 
configRequestEntity.getRequest().getComponentId());
+
+            return getRequestBuilder(target).post(
+                    Entity.entity(configRequestEntity, 
MediaType.APPLICATION_JSON_TYPE),
+                    VerifyConfigRequestEntity.class
+            );
+        });
+    }
+
+    @Override
+    public VerifyConfigRequestEntity 
getFlowAnalysisRuleConfigVerificationRequest(final String taskId, final String 
verificationRequestId) throws NiFiClientException, IOException {
+        if (verificationRequestId == null) {
+            throw new IllegalArgumentException("Verification Request ID cannot 
be null");
+        }
+
+        return executeAction("Error retrieving Flow Analysis Rule Config 
Verification Request", () -> {
+            final WebTarget target = controllerTarget
+                    
.path("flow-analysis-rules/{id}/config/verification-requests/{requestId}")
+                    .resolveTemplate("id", taskId)
+                    .resolveTemplate("requestId", verificationRequestId);
+
+            return 
getRequestBuilder(target).get(VerifyConfigRequestEntity.class);
+        });
+    }
+
+    @Override
+    public VerifyConfigRequestEntity 
deleteFlowAnalysisRuleConfigVerificationRequest(final String taskId, final 
String verificationRequestId) throws NiFiClientException, IOException {
+        if (verificationRequestId == null) {
+            throw new IllegalArgumentException("Verification Request ID cannot 
be null");
+        }
+
+        return executeAction("Error deleting Flow Analysis Rule Config 
Verification Request", () -> {
+            final WebTarget target = controllerTarget
+                    
.path("flow-analysis-rules/{id}/config/verification-requests/{requestId}")
+                    .resolveTemplate("id", taskId)
+                    .resolveTemplate("requestId", verificationRequestId);
+
+            return 
getRequestBuilder(target).delete(VerifyConfigRequestEntity.class);
+        });
+    }
+
     @Override
     public ParameterProviderEntity createParamProvider(final 
ParameterProviderEntity paramProvider) throws NiFiClientException, IOException {
         if (paramProvider == null) {
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
index 900ed791cd..21fbe51b85 100644
--- 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/CommandOption.java
@@ -105,6 +105,9 @@ public enum CommandOption {
     // NiFi - Reporting Tasks
     RT_ID("rt", "reportingTaskId", "The id of a reporting task", true),
 
+    // NiFi - Flow Analysis Rules
+    FAR_ID("far", "flowAnalysisRuleId", "The id of a flow analysis rule", 
true),
+
     // NiFi - User/Group
     USER_NAME("un", "userName", "The name of a user", true),
     USER_ID("ui", "userIdentifier", "The identifier of a user", true),
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 21fc6779ac..a5af683995 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
@@ -27,11 +27,17 @@ import 
org.apache.nifi.toolkit.cli.impl.command.nifi.cs.EnableControllerServices
 import org.apache.nifi.toolkit.cli.impl.command.nifi.cs.GetControllerService;
 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.CreateFlowAnalysisRule;
 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.DeleteFlowAnalysisRule;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.flow.DisableFlowAnalysisRules;
+import 
org.apache.nifi.toolkit.cli.impl.command.nifi.flow.EnableFlowAnalysisRules;
 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.GetFlowAnalysisRule;
+import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetFlowAnalysisRules;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetReportingTask;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetReportingTasks;
 import org.apache.nifi.toolkit.cli.impl.command.nifi.flow.GetRootId;
@@ -160,6 +166,12 @@ public class NiFiCommandGroup extends AbstractCommandGroup 
{
         commands.add(new ExportReportingTasks());
         commands.add(new ExportReportingTask());
         commands.add(new ImportReportingTasks());
+        commands.add(new CreateFlowAnalysisRule());
+        commands.add(new GetFlowAnalysisRules());
+        commands.add(new GetFlowAnalysisRule());
+        commands.add(new EnableFlowAnalysisRules());
+        commands.add(new DisableFlowAnalysisRules());
+        commands.add(new DeleteFlowAnalysisRule());
         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/CreateFlowAnalysisRule.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CreateFlowAnalysisRule.java
new file mode 100644
index 0000000000..e87ae017c8
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/CreateFlowAnalysisRule.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.commons.io.IOUtils;
+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.ControllerClient;
+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.StringResult;
+import org.apache.nifi.toolkit.cli.impl.util.JacksonUtils;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+/**
+ * Command for creating a flow analysis rule.
+ */
+public class CreateFlowAnalysisRule extends AbstractNiFiCommand<StringResult> {
+
+    public CreateFlowAnalysisRule() {
+        super("create-flow-analysis-rule", StringResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Creates a flow analysis rule from a local file.";
+    }
+
+    @Override
+    public void doInitialize(final Context context) {
+        addOption(CommandOption.INPUT_SOURCE.createOption());
+    }
+
+    @Override
+    public StringResult doExecute(final NiFiClient client, final Properties 
properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+        final String inputFile = getRequiredArg(properties, 
CommandOption.INPUT_SOURCE);
+        final URI uri = Paths.get(inputFile).toAbsolutePath().toUri();
+        final String contents = IOUtils.toString(uri, StandardCharsets.UTF_8);
+
+        final ObjectMapper objectMapper = JacksonUtils.getObjectMapper();
+        final FlowAnalysisRuleEntity deserializedTask = 
objectMapper.readValue(contents, FlowAnalysisRuleEntity.class);
+        if (deserializedTask == null) {
+            throw new IOException("Unable to deserialize flow analysis rule 
from " + inputFile);
+        }
+
+        deserializedTask.setRevision(getInitialRevisionDTO());
+
+        final ControllerClient controllerClient = client.getControllerClient();
+        final FlowAnalysisRuleEntity createdEntity = 
controllerClient.createFlowAnalysisRule(deserializedTask);
+
+        return new StringResult(String.valueOf(createdEntity.getId()), 
getContext().isInteractive());
+    }
+
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/DeleteFlowAnalysisRule.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/DeleteFlowAnalysisRule.java
new file mode 100644
index 0000000000..20ca24c000
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/DeleteFlowAnalysisRule.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.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+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.FlowAnalysisRuleResult;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for deleting a flow analysis rule.
+ */
+public class DeleteFlowAnalysisRule extends 
AbstractNiFiCommand<FlowAnalysisRuleResult> {
+
+    public DeleteFlowAnalysisRule() {
+        super("delete-flow-analysis-rule", FlowAnalysisRuleResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Delete a flow analysis rule.";
+    }
+
+    @Override
+    public void doInitialize(final Context context) {
+        addOption(CommandOption.FAR_ID.createOption());
+    }
+
+    @Override
+    public FlowAnalysisRuleResult doExecute(final NiFiClient client, final 
Properties properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+
+        final String flowAnalysisRuleId = getRequiredArg(properties, 
CommandOption.FAR_ID);
+
+        final ControllerClient controllerClient = client.getControllerClient();
+        FlowAnalysisRuleEntity flowAnalysisRule = 
controllerClient.getFlowAnalysisRule(flowAnalysisRuleId);
+        final FlowAnalysisRuleEntity deletedFlowAnalysisRuleEntity = 
controllerClient.deleteFlowAnalysisRule(flowAnalysisRule);
+
+        return new FlowAnalysisRuleResult(getResultType(properties), 
deletedFlowAnalysisRuleEntity);
+    }
+
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/DisableFlowAnalysisRules.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/DisableFlowAnalysisRules.java
new file mode 100644
index 0000000000..ab152abb65
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/DisableFlowAnalysisRules.java
@@ -0,0 +1,98 @@
+/*
+ * 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.commons.lang3.StringUtils;
+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.AbstractNiFiActivateCommand;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleRunStatusEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Command for disabling flow analysis rule.
+ */
+public class DisableFlowAnalysisRules extends 
AbstractNiFiActivateCommand<FlowAnalysisRuleEntity,
+        FlowAnalysisRuleRunStatusEntity> {
+
+    public DisableFlowAnalysisRules() {
+        super("disable-flow-analysis-rules");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Attempts to disable one or all flow analysis rule(s). In 
stand-alone mode this command " +
+            "will not produce all of the output seen in interactive mode 
unless the --verbose argument is specified.";
+    }
+
+    @Override
+    protected void doInitialize(final Context context) {
+        addOption(CommandOption.FAR_ID.createOption());
+    }
+
+    @Override
+    public VoidResult doExecute(
+            final NiFiClient client,
+            final Properties properties
+    ) throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+        final String ruleId = getArg(properties, CommandOption.FAR_ID);
+        final Set<FlowAnalysisRuleEntity> ruleEntities = new HashSet<>();
+
+        if (StringUtils.isBlank(ruleId)) {
+            final FlowAnalysisRulesEntity rulesEntity = 
client.getControllerClient().getFlowAnalysisRules();
+            ruleEntities.addAll(rulesEntity.getFlowAnalysisRules());
+        } else {
+            
ruleEntities.add(client.getControllerClient().getFlowAnalysisRule(ruleId));
+        }
+
+        activate(client, properties, ruleEntities, "DISABLED");
+
+        return VoidResult.getInstance();
+    }
+
+    @Override
+    public FlowAnalysisRuleRunStatusEntity getRunStatusEntity() {
+        return new FlowAnalysisRuleRunStatusEntity();
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity activateComponent(
+            final NiFiClient client,
+            final FlowAnalysisRuleEntity ruleEntity,
+            final FlowAnalysisRuleRunStatusEntity runStatusEntity
+    ) throws NiFiClientException, IOException {
+        return 
client.getControllerClient().activateFlowAnalysisRule(ruleEntity.getId(), 
runStatusEntity);
+    }
+
+    @Override
+    public String getDispName(final FlowAnalysisRuleEntity ruleEntity) {
+        return "Flow analysis rule \"" + ruleEntity.getComponent().getName() + 
"\" " +
+                "(id: " + ruleEntity.getId() + ")";
+    }
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/EnableFlowAnalysisRules.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/EnableFlowAnalysisRules.java
new file mode 100644
index 0000000000..7ee2d8ae04
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/EnableFlowAnalysisRules.java
@@ -0,0 +1,98 @@
+/*
+ * 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.commons.lang3.StringUtils;
+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.AbstractNiFiActivateCommand;
+import org.apache.nifi.toolkit.cli.impl.result.VoidResult;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleRunStatusEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Command for enabling flow analysis rule.
+ */
+public class EnableFlowAnalysisRules extends 
AbstractNiFiActivateCommand<FlowAnalysisRuleEntity,
+        FlowAnalysisRuleRunStatusEntity> {
+
+    public EnableFlowAnalysisRules() {
+        super("enable-flow-analysis-rules");
+    }
+
+    @Override
+    public String getDescription() {
+        return "Attempts to enable one or all flow analysis rule(s). In 
stand-alone mode this command " +
+            "will not produce all of the output seen in interactive mode 
unless the --verbose argument is specified.";
+    }
+
+    @Override
+    protected void doInitialize(final Context context) {
+        addOption(CommandOption.FAR_ID.createOption());
+    }
+
+    @Override
+    public VoidResult doExecute(
+            final NiFiClient client,
+            final Properties properties
+    ) throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+        final String ruleId = getArg(properties, CommandOption.FAR_ID);
+        final Set<FlowAnalysisRuleEntity> ruleEntities = new HashSet<>();
+
+        if (StringUtils.isBlank(ruleId)) {
+            final FlowAnalysisRulesEntity rulesEntity = 
client.getControllerClient().getFlowAnalysisRules();
+            ruleEntities.addAll(rulesEntity.getFlowAnalysisRules());
+        } else {
+            
ruleEntities.add(client.getControllerClient().getFlowAnalysisRule(ruleId));
+        }
+
+        activate(client, properties, ruleEntities, "ENABLED");
+
+        return VoidResult.getInstance();
+    }
+
+    @Override
+    public FlowAnalysisRuleRunStatusEntity getRunStatusEntity() {
+        return new FlowAnalysisRuleRunStatusEntity();
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity activateComponent(
+            final NiFiClient client,
+            final FlowAnalysisRuleEntity ruleEntity,
+            final FlowAnalysisRuleRunStatusEntity runStatusEntity
+    ) throws NiFiClientException, IOException {
+        return 
client.getControllerClient().activateFlowAnalysisRule(ruleEntity.getId(), 
runStatusEntity);
+    }
+
+    @Override
+    public String getDispName(final FlowAnalysisRuleEntity ruleEntity) {
+        return "Flow analysis rule \"" + ruleEntity.getComponent().getName() + 
"\" " +
+                "(id: " + ruleEntity.getId() + ")";
+    }
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetFlowAnalysisRule.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetFlowAnalysisRule.java
new file mode 100644
index 0000000000..8279789e9b
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetFlowAnalysisRule.java
@@ -0,0 +1,62 @@
+/*
+ * 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.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.api.Context;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+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.FlowAnalysisRuleResult;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command for retrieving a flow analysis rule.
+ */
+public class GetFlowAnalysisRule extends 
AbstractNiFiCommand<FlowAnalysisRuleResult> {
+
+    public GetFlowAnalysisRule() {
+        super("get-flow-analysis-rule", FlowAnalysisRuleResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Retrieves a flow analysis rule.";
+    }
+
+    @Override
+    protected void doInitialize(final Context context) {
+        addOption(CommandOption.FAR_ID.createOption());
+    }
+
+    @Override
+    public FlowAnalysisRuleResult doExecute(final NiFiClient client, final 
Properties properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+        final String ruleId = getRequiredArg(properties, CommandOption.FAR_ID);
+        final ControllerClient controllerClient = client.getControllerClient();
+
+        final FlowAnalysisRuleEntity ruleEntity = 
controllerClient.getFlowAnalysisRule(ruleId);
+        return new FlowAnalysisRuleResult(getResultType(properties), 
ruleEntity);
+    }
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetFlowAnalysisRules.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetFlowAnalysisRules.java
new file mode 100644
index 0000000000..aa9e2734d6
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/command/nifi/flow/GetFlowAnalysisRules.java
@@ -0,0 +1,52 @@
+/*
+ * 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.toolkit.cli.api.CommandException;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.ControllerClient;
+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.nifi.AbstractNiFiCommand;
+import org.apache.nifi.toolkit.cli.impl.result.nifi.FlowAnalysisRulesResult;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Command to get the list of flow analysis rules.
+ */
+public class GetFlowAnalysisRules extends 
AbstractNiFiCommand<FlowAnalysisRulesResult> {
+
+    public GetFlowAnalysisRules() {
+        super("get-flow-analysis-rules", FlowAnalysisRulesResult.class);
+    }
+
+    @Override
+    public String getDescription() {
+        return "Retrieves the list of flow analysis rules.";
+    }
+
+    @Override
+    public FlowAnalysisRulesResult doExecute(NiFiClient client, Properties 
properties)
+            throws NiFiClientException, IOException, MissingOptionException, 
CommandException {
+        final ControllerClient controllerClient = client.getControllerClient();
+        final FlowAnalysisRulesEntity tasksEntity = 
controllerClient.getFlowAnalysisRules();
+        return new FlowAnalysisRulesResult(getResultType(properties), 
tasksEntity);
+    }
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/FlowAnalysisRuleResult.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/FlowAnalysisRuleResult.java
new file mode 100644
index 0000000000..f97b41f415
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/FlowAnalysisRuleResult.java
@@ -0,0 +1,53 @@
+/*
+ * 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.toolkit.cli.api.ResultType;
+import org.apache.nifi.toolkit.cli.impl.result.AbstractWritableResult;
+import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.FlowAnalysisRuleDTO;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Objects;
+
+public class FlowAnalysisRuleResult extends 
AbstractWritableResult<FlowAnalysisRuleEntity> {
+
+    private final FlowAnalysisRuleEntity flowAnalysisRuleEntity;
+
+    public FlowAnalysisRuleResult(final ResultType resultType, final 
FlowAnalysisRuleEntity flowAnalysisRuleEntity) {
+        super(resultType);
+        this.flowAnalysisRuleEntity = 
Objects.requireNonNull(flowAnalysisRuleEntity);
+    }
+
+    @Override
+    public FlowAnalysisRuleEntity getResult() {
+        return flowAnalysisRuleEntity;
+    }
+
+    @Override
+    protected void writeSimpleResult(final PrintStream output) throws 
IOException {
+        final FlowAnalysisRuleDTO flowAnalysisRuleDTO = 
flowAnalysisRuleEntity.getComponent();
+
+        final BundleDTO bundle = flowAnalysisRuleDTO.getBundle();
+        output.printf("Name  : %s\nID    : %s\nType  : %s\nBundle: %s - %s 
%s\nState : %s\n",
+                flowAnalysisRuleDTO.getName(), flowAnalysisRuleDTO.getId(), 
flowAnalysisRuleDTO.getType(),
+                bundle.getGroup(), bundle.getArtifact(), bundle.getVersion(), 
flowAnalysisRuleDTO.getState());
+    }
+}
diff --git 
a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/FlowAnalysisRulesResult.java
 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/FlowAnalysisRulesResult.java
new file mode 100644
index 0000000000..02e16d97fd
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/result/nifi/FlowAnalysisRulesResult.java
@@ -0,0 +1,88 @@
+/*
+ * 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.toolkit.cli.api.ResultType;
+import org.apache.nifi.toolkit.cli.impl.result.AbstractWritableResult;
+import org.apache.nifi.toolkit.cli.impl.result.writer.DynamicTableWriter;
+import org.apache.nifi.toolkit.cli.impl.result.writer.Table;
+import org.apache.nifi.toolkit.cli.impl.result.writer.TableWriter;
+import org.apache.nifi.web.api.dto.FlowAnalysisRuleDTO;
+import org.apache.nifi.web.api.entity.FlowAnalysisRuleEntity;
+import org.apache.nifi.web.api.entity.FlowAnalysisRulesEntity;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Result for FlowAnalysisRulesEntity.
+ */
+public class FlowAnalysisRulesResult extends 
AbstractWritableResult<FlowAnalysisRulesEntity> {
+
+    private final FlowAnalysisRulesEntity flowAnalysisRulesEntity;
+
+    public FlowAnalysisRulesResult(final ResultType resultType, final 
FlowAnalysisRulesEntity flowAnalysisRulesEntity) {
+        super(resultType);
+        this.flowAnalysisRulesEntity = 
Objects.requireNonNull(flowAnalysisRulesEntity);
+    }
+
+    @Override
+    protected void writeSimpleResult(final PrintStream output) throws 
IOException {
+        final Set<FlowAnalysisRuleEntity> ruleEntities = 
flowAnalysisRulesEntity.getFlowAnalysisRules();
+        if (ruleEntities == null) {
+            return;
+        }
+
+        final List<FlowAnalysisRuleDTO> ruleDTOS = ruleEntities.stream()
+                .map(FlowAnalysisRuleEntity::getComponent)
+                .sorted(Comparator.comparing(FlowAnalysisRuleDTO::getName))
+                .collect(Collectors.toList());
+
+        final Table table = new Table.Builder()
+                .column("#", 3, 3, false)
+                .column("Name", 5, 40, true)
+                .column("ID", 36, 36, false)
+                .column("Type", 5, 40, true)
+                .column("State", 10, 20, false)
+                .build();
+
+        for (int i = 0; i < ruleDTOS.size(); i++) {
+            final FlowAnalysisRuleDTO ruleDTO = ruleDTOS.get(i);
+            final String[] typeSplit = ruleDTO.getType().split("\\.", -1);
+            table.addRow(
+                    String.valueOf(i + 1),
+                    ruleDTO.getName(),
+                    ruleDTO.getId(),
+                    typeSplit[typeSplit.length - 1],
+                    ruleDTO.getState()
+            );
+        }
+
+        final TableWriter tableWriter = new DynamicTableWriter();
+        tableWriter.write(table, output);
+    }
+
+    @Override
+    public FlowAnalysisRulesEntity getResult() {
+        return flowAnalysisRulesEntity;
+    }
+}

Reply via email to