diff --git a/minifi/minifi-commons/minifi-commons-api/src/main/java/org/apache/nifi/minifi/commons/api/MiNiFiProperties.java b/minifi/minifi-commons/minifi-commons-api/src/main/java/org/apache/nifi/minifi/commons/api/MiNiFiProperties.java index 07e2c16a501c..b346eed20be8 100644 --- a/minifi/minifi-commons/minifi-commons-api/src/main/java/org/apache/nifi/minifi/commons/api/MiNiFiProperties.java +++ b/minifi/minifi-commons/minifi-commons-api/src/main/java/org/apache/nifi/minifi/commons/api/MiNiFiProperties.java @@ -81,8 +81,8 @@ public enum MiNiFiProperties { C2_KEEP_ALIVE_DURATION("c2.rest.keepAliveDuration", "5 min", false, true, TIME_PERIOD_VALIDATOR), C2_REST_HTTP_HEADERS("c2.rest.http.headers", "Accept:application/json", false, true, VALID), C2_CONFIG_DIRECTORY("c2.config.directory", "./conf", false, true, VALID), - C2_RUNTIME_MANIFEST_IDENTIFIER("c2.runtime.manifest.identifier", "", false, true, VALID), - C2_RUNTIME_TYPE("c2.runtime.type", "", false, true, VALID), + C2_RUNTIME_MANIFEST_IDENTIFIER("c2.runtime.manifest.identifier", "minifi", false, true, VALID), + C2_RUNTIME_TYPE("c2.runtime.type", "minifi-java", false, true, VALID), C2_ASSET_DIRECTORY("c2.asset.directory", "./asset", false, true, VALID), C2_SECURITY_TRUSTSTORE_LOCATION("c2.security.truststore.location", "", false, false, VALID), C2_SECURITY_TRUSTSTORE_PASSWORD("c2.security.truststore.password", "", true, false, VALID), diff --git a/nifi-commons/nifi-metrics/pom.xml b/nifi-commons/nifi-metrics/pom.xml index 8025ad7809c3..5743be5c7097 100644 --- a/nifi-commons/nifi-metrics/pom.xml +++ b/nifi-commons/nifi-metrics/pom.xml @@ -31,12 +31,12 @@ io.dropwizard.metrics metrics-jvm - 4.2.25 + 4.2.26 io.dropwizard.metrics metrics-core - 4.2.25 + 4.2.26 diff --git a/nifi-commons/nifi-property-protection-azure/pom.xml b/nifi-commons/nifi-property-protection-azure/pom.xml index 5e7ba04adc8a..96f939853c89 100644 --- a/nifi-commons/nifi-property-protection-azure/pom.xml +++ b/nifi-commons/nifi-property-protection-azure/pom.xml @@ -98,7 +98,7 @@ com.microsoft.azure msal4j - 1.15.0 + 1.15.1 diff --git a/nifi-commons/nifi-record-path/pom.xml b/nifi-commons/nifi-record-path/pom.xml index 7aa53b9b4e8c..3548b176041b 100644 --- a/nifi-commons/nifi-record-path/pom.xml +++ b/nifi-commons/nifi-record-path/pom.xml @@ -125,5 +125,12 @@ com.fasterxml.jackson.core jackson-core + + + org.apache.nifi + nifi-json-record-utils + 2.0.0-SNAPSHOT + test + diff --git a/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java b/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java index a548ccd812f5..2513b92ee125 100644 --- a/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java +++ b/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java @@ -17,7 +17,13 @@ package org.apache.nifi.record.path; +import org.apache.nifi.json.JsonRecordSource; +import org.apache.nifi.json.JsonSchemaInference; +import org.apache.nifi.json.JsonTreeRowRecordReader; +import org.apache.nifi.logging.ComponentLog; import org.apache.nifi.record.path.exception.RecordPathException; +import org.apache.nifi.schema.inference.TimeValueInference; +import org.apache.nifi.serialization.MalformedRecordException; import org.apache.nifi.serialization.SimpleRecordSchema; import org.apache.nifi.serialization.record.DataType; import org.apache.nifi.serialization.record.MapRecord; @@ -31,7 +37,10 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.StandardCharsets; import java.sql.Date; @@ -241,11 +250,67 @@ public void testDescendantField() { } @Test - public void testParent() { + public void testDescendantFieldWithArrayOfRecords() throws IOException, MalformedRecordException { + final String recordJson = """ + { + "container" : { + "id" : "0", + "metadata" : { + "filename" : "file1.pdf", + "page.count" : "165" + }, + "textElement" : null, + "containers" : [ { + "id" : "1", + "title" : null, + "metadata" : { + "end.page" : 1, + "start.page" : 1 + }, + "textElement" : { + "text" : "Table of Contents", + "metadata" : { } + }, + "containers" : [ ] + } ] + } + } + """; + + final JsonSchemaInference schemaInference = new JsonSchemaInference(new TimeValueInference("MM/dd/yyyy", "HH:mm:ss", "MM/dd/yyyy HH:mm:ss")); + final JsonRecordSource jsonRecordSource = new JsonRecordSource(new ByteArrayInputStream(recordJson.getBytes(StandardCharsets.UTF_8))); + final RecordSchema schema = schemaInference.inferSchema(jsonRecordSource); + + final JsonTreeRowRecordReader reader = new JsonTreeRowRecordReader(new ByteArrayInputStream(recordJson.getBytes(StandardCharsets.UTF_8)), Mockito.mock(ComponentLog.class), + schema, "MM/dd/yyyy", "HH:mm:ss", "MM/dd/yyyy HH:mm:ss"); + final Record record = reader.nextRecord(); + + final List fieldValues = RecordPath.compile("//textElement[./text = 'Table of Contents']/metadata/insertion").evaluate(record).getSelectedFields().toList(); + assertEquals(1, fieldValues.size()); + fieldValues.getFirst().updateValue("Hello"); + record.incorporateInactiveFields(); + + final Record container = (Record) record.getValue("container"); + final Object[] containers = (Object[]) container.getValue("containers"); + final Record textElement = (Record) (((Record) containers[0]).getValue("textElement")); + final Record metadata = (Record) textElement.getValue("metadata"); + assertEquals("Hello", metadata.getValue("insertion")); + + final List metadataFields = metadata.getSchema().getFields(); + assertEquals(1, metadataFields.size()); + assertEquals("insertion", metadataFields.getFirst().getFieldName()); + } + + private Record createAccountRecord(final int id, final double balance) { final Map accountValues = new HashMap<>(); - accountValues.put("id", 1); - accountValues.put("balance", 123.45D); - final Record accountRecord = new MapRecord(getAccountSchema(), accountValues); + accountValues.put("id", id); + accountValues.put("balance", balance); + return new MapRecord(getAccountSchema(), accountValues); + } + + @Test + public void testParent() { + final Record accountRecord = createAccountRecord(1, 123.45D); final RecordSchema schema = new SimpleRecordSchema(getDefaultFields()); final Map values = new HashMap<>(); @@ -2234,9 +2299,24 @@ private List getDefaultFields() { final DataType accountsType = RecordFieldType.ARRAY.getArrayDataType(accountDataType); final RecordField accountsField = new RecordField("accounts", accountsType); fields.add(accountsField); + + final DataType bankType = RecordFieldType.CHOICE.getChoiceDataType( + RecordFieldType.STRING.getDataType(), + RecordFieldType.RECORD.getRecordDataType(getBankSchema()) + ); + final RecordField banksField = new RecordField("banks", RecordFieldType.ARRAY.getArrayDataType(bankType)); + fields.add(banksField); + return fields; } + private RecordSchema getBankSchema() { + final DataType accountDataType = RecordFieldType.RECORD.getRecordDataType(getAccountSchema()); + final DataType accountsType = RecordFieldType.ARRAY.getArrayDataType(accountDataType); + final RecordSchema bankSchema = new SimpleRecordSchema(List.of(new RecordField("accounts", accountsType))); + return bankSchema; + } + private RecordSchema getAccountSchema() { final List accountFields = new ArrayList<>(); accountFields.add(new RecordField("id", RecordFieldType.INT.getDataType())); diff --git a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java index 90c6a53c0b10..aaf459a001a4 100644 --- a/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java +++ b/nifi-commons/nifi-record/src/main/java/org/apache/nifi/serialization/record/util/DataTypeUtils.java @@ -1884,9 +1884,43 @@ private static Optional getWiderRecordType(final RecordDataType thisRe return Optional.of(otherRecordDataType); } + // Check if all fields in 'thisSchema' are equal to or wider than all fields in 'otherSchema' + if (isRecordWider(thisSchema, otherSchema)) { + return Optional.of(thisRecordDataType); + } + if (isRecordWider(otherSchema, thisSchema)) { + return Optional.of(otherRecordDataType); + } + return Optional.empty(); } + public static boolean isRecordWider(final RecordSchema potentiallyWider, final RecordSchema potentiallyNarrower) { + final List narrowerFields = potentiallyNarrower.getFields(); + + for (final RecordField narrowerField : narrowerFields) { + final Optional widerField = potentiallyWider.getField(narrowerField.getFieldName()); + if (widerField.isEmpty()) { + return false; + } + + if (widerField.get().getDataType().equals(narrowerField.getDataType())) { + continue; + } + + final Optional widerType = getWiderType(widerField.get().getDataType(), narrowerField.getDataType()); + if (widerType.isEmpty()) { + return false; + } + + if (!widerType.get().equals(widerField.get().getDataType())) { + return false; + } + } + + return true; + } + private static boolean isDecimalType(final RecordFieldType fieldType) { return switch (fieldType) { case FLOAT, DOUBLE, DECIMAL -> true; diff --git a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/TestDataTypeUtils.java b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/TestDataTypeUtils.java index bd84b449386b..d5faca787eca 100644 --- a/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/TestDataTypeUtils.java +++ b/nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/TestDataTypeUtils.java @@ -111,6 +111,67 @@ public void testWiderRecordWhenAllFieldsContainedWithin() { assertEquals(((RecordDataType) widerType.get()).getChildSchema(), widerRecord.getSchema()); } + @Test + public void testWiderRecordWhenChildRecordHasAllFieldsContainedWithin() { + final Record jane = DataTypeUtils.toRecord(Map.of( + "name", "Jane" + ), ""); + + final Record smallRecord = DataTypeUtils.toRecord(Map.of( + "firstName", "John", + "lastName", "Doe", + "child", jane, + "age", 30), ""); + + final Record janeWithAge = DataTypeUtils.toRecord(Map.of( + "name", "Jane", + "age", 2 + ), ""); + + final Record widerRecord = DataTypeUtils.toRecord(Map.of( + "firstName", "John", + "lastName", "Doe", + "fullName", "John Doe", + "child", janeWithAge, + "age", 30), ""); + + final Optional widerType = DataTypeUtils.getWiderType(RecordFieldType.RECORD.getRecordDataType(smallRecord.getSchema()), + RecordFieldType.RECORD.getRecordDataType(widerRecord.getSchema())); + assertTrue(widerType.isPresent()); + assertEquals(((RecordDataType) widerType.get()).getChildSchema(), widerRecord.getSchema()); + } + + @Test + public void testIsRecordWiderWithExtraField() { + final Record jane = DataTypeUtils.toRecord(Map.of( + ), ""); + + final Record smallRecord = DataTypeUtils.toRecord(Map.of( + "firstName", "John", + "lastName", "Doe", + "child", jane, + "age", 30), ""); + + final Record janeWithAge = DataTypeUtils.toRecord(Map.of( + "name", "Jane", + "age", 2 + ), ""); + + final Record widerRecord = DataTypeUtils.toRecord(Map.of( + "firstName", "John", + "lastName", "Doe", + "fullName", "John Doe", + "child", janeWithAge, + "age", 30), ""); + + assertFalse(DataTypeUtils.isRecordWider(smallRecord.getSchema(), widerRecord.getSchema())); + assertTrue(DataTypeUtils.isRecordWider(widerRecord.getSchema(), smallRecord.getSchema())); + + assertFalse(DataTypeUtils.isRecordWider(jane.getSchema(), janeWithAge.getSchema())); + assertTrue(DataTypeUtils.isRecordWider(janeWithAge.getSchema(), jane.getSchema())); + } + + @Test public void testWiderRecordDifferingFields() { final Record firstRecord = DataTypeUtils.toRecord(Map.of( diff --git a/nifi-docs/src/main/asciidoc/toolkit-guide.adoc b/nifi-docs/src/main/asciidoc/toolkit-guide.adoc index b6ef3ec56563..70f0ea8286fa 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-extension-bom/pom.xml b/nifi-extension-bom/pom.xml index 6cf9f75b4a38..57f806867fd9 100644 --- a/nifi-extension-bom/pom.xml +++ b/nifi-extension-bom/pom.xml @@ -238,7 +238,7 @@ org.eclipse.jdt ecj - 3.37.0 + 3.38.0 provided diff --git a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/pom.xml b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/pom.xml index 0563412de0ac..798bdea31d01 100644 --- a/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/pom.xml +++ b/nifi-extension-bundles/nifi-azure-bundle/nifi-azure-processors/pom.xml @@ -111,7 +111,7 @@ io.projectreactor reactor-core - 3.6.6 + 3.6.7 com.azure @@ -191,7 +191,7 @@ io.projectreactor reactor-test - 3.6.6 + 3.6.7 test diff --git a/nifi-extension-bundles/nifi-azure-bundle/pom.xml b/nifi-extension-bundles/nifi-azure-bundle/pom.xml index 9592a0179499..7fba44a572b0 100644 --- a/nifi-extension-bundles/nifi-azure-bundle/pom.xml +++ b/nifi-extension-bundles/nifi-azure-bundle/pom.xml @@ -29,7 +29,7 @@ 1.2.24 - 1.15.0 + 1.15.1 0.34.1 diff --git a/nifi-extension-bundles/nifi-box-bundle/pom.xml b/nifi-extension-bundles/nifi-box-bundle/pom.xml index 4a473d90eedf..ad3d2e7671a0 100644 --- a/nifi-extension-bundles/nifi-box-bundle/pom.xml +++ b/nifi-extension-bundles/nifi-box-bundle/pom.xml @@ -39,7 +39,7 @@ com.box box-java-sdk - 4.9.1 + 4.10.0 diff --git a/nifi-extension-bundles/nifi-elasticsearch-bundle/pom.xml b/nifi-extension-bundles/nifi-elasticsearch-bundle/pom.xml index 6b9506f9b784..b2239d1bd438 100644 --- a/nifi-extension-bundles/nifi-elasticsearch-bundle/pom.xml +++ b/nifi-extension-bundles/nifi-elasticsearch-bundle/pom.xml @@ -33,7 +33,7 @@ language governing permissions and limitations under the License. --> - 8.13.4 + 8.14.1 @@ -88,7 +88,7 @@ language governing permissions and limitations under the License. --> - 8.13.4 + 8.14.1 s3cret diff --git a/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml b/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml index e67afa41cf29..4f390c21fe90 100644 --- a/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml +++ b/nifi-extension-bundles/nifi-email-bundle/nifi-email-processors/pom.xml @@ -25,7 +25,7 @@ nifi-email-processors jar - 6.2.5 + 6.3.0 5.2.5 diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-file-transfer/src/main/java/org/apache/nifi/processor/util/file/transfer/FileTransfer.java b/nifi-extension-bundles/nifi-extension-utils/nifi-file-transfer/src/main/java/org/apache/nifi/processor/util/file/transfer/FileTransfer.java index 56f6f1bfa72a..969f51791bd7 100644 --- a/nifi-extension-bundles/nifi-extension-utils/nifi-file-transfer/src/main/java/org/apache/nifi/processor/util/file/transfer/FileTransfer.java +++ b/nifi-extension-bundles/nifi-extension-utils/nifi-file-transfer/src/main/java/org/apache/nifi/processor/util/file/transfer/FileTransfer.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.util.List; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.Validator; import org.apache.nifi.expression.ExpressionLanguageScope; @@ -207,17 +208,49 @@ default String getAbsolutePath(FlowFile flowFile, String remotePath) throws IOEx public static final String FILE_MODIFY_DATE_ATTR_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; public static final String CONFLICT_RESOLUTION_REPLACE = "REPLACE"; + AllowableValue CONFLICT_RESOLUTION_REPLACE_ALLOWABLE = + new AllowableValue(CONFLICT_RESOLUTION_REPLACE, CONFLICT_RESOLUTION_REPLACE, + "Remote file is replaced with new file, FlowFile goes to success"); + public static final String CONFLICT_RESOLUTION_RENAME = "RENAME"; + AllowableValue CONFLICT_RESOLUTION_RENAME_ALLOWABLE = + new AllowableValue(CONFLICT_RESOLUTION_RENAME, CONFLICT_RESOLUTION_RENAME, + "New file is renamed with a one-up number at the beginning, FlowFile goes to success"); + public static final String CONFLICT_RESOLUTION_IGNORE = "IGNORE"; + AllowableValue CONFLICT_RESOLUTION_IGNORE_ALLOWABLE = + new AllowableValue(CONFLICT_RESOLUTION_IGNORE, CONFLICT_RESOLUTION_IGNORE, + "File is not transferred, FlowFile goes to success"); + public static final String CONFLICT_RESOLUTION_REJECT = "REJECT"; + AllowableValue CONFLICT_RESOLUTION_REJECT_ALLOWABLE = + new AllowableValue(CONFLICT_RESOLUTION_REJECT, CONFLICT_RESOLUTION_REJECT, + "File is not transferred, FlowFile goes to reject"); + public static final String CONFLICT_RESOLUTION_FAIL = "FAIL"; + AllowableValue CONFLICT_RESOLUTION_FAIL_ALLOWABLE = + new AllowableValue(CONFLICT_RESOLUTION_FAIL, CONFLICT_RESOLUTION_FAIL, + "File is not transferred, FlowFile goes to failure"); + public static final String CONFLICT_RESOLUTION_NONE = "NONE"; + AllowableValue CONFLICT_RESOLUTION_NONE_ALLOWABLE = + new AllowableValue(CONFLICT_RESOLUTION_NONE, CONFLICT_RESOLUTION_NONE, + "Do not check for conflict before transfer, FlowFile goes to success or failure"); + + AllowableValue[] CONFLICT_RESOLUTION_ALLOWABLE_VALUES = new AllowableValue[] { + CONFLICT_RESOLUTION_REPLACE_ALLOWABLE, + CONFLICT_RESOLUTION_IGNORE_ALLOWABLE, + CONFLICT_RESOLUTION_RENAME_ALLOWABLE, + CONFLICT_RESOLUTION_REJECT_ALLOWABLE, + CONFLICT_RESOLUTION_FAIL_ALLOWABLE, + CONFLICT_RESOLUTION_NONE_ALLOWABLE}; + public static final PropertyDescriptor CONFLICT_RESOLUTION = new PropertyDescriptor.Builder() .name("Conflict Resolution") .description("Determines how to handle the problem of filename collisions") .required(true) - .allowableValues(CONFLICT_RESOLUTION_REPLACE, CONFLICT_RESOLUTION_IGNORE, CONFLICT_RESOLUTION_RENAME, CONFLICT_RESOLUTION_REJECT, CONFLICT_RESOLUTION_FAIL, CONFLICT_RESOLUTION_NONE) - .defaultValue(CONFLICT_RESOLUTION_NONE) + .allowableValues(CONFLICT_RESOLUTION_ALLOWABLE_VALUES) + .defaultValue(CONFLICT_RESOLUTION_NONE_ALLOWABLE) .build(); public static final PropertyDescriptor REJECT_ZERO_BYTE = new PropertyDescriptor.Builder() .name("Reject Zero-Byte Files") diff --git a/nifi-extension-bundles/nifi-provenance-repository-bundle/pom.xml b/nifi-extension-bundles/nifi-provenance-repository-bundle/pom.xml index db5104a49cde..2d87e5217945 100644 --- a/nifi-extension-bundles/nifi-provenance-repository-bundle/pom.xml +++ b/nifi-extension-bundles/nifi-provenance-repository-bundle/pom.xml @@ -27,7 +27,7 @@ nifi-provenance-repository-nar - 9.10.0 + 9.11.0 diff --git a/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/FlowFileTransformProxy.java b/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/FlowFileTransformProxy.java index 073158f59be7..4d654d7c4eda 100644 --- a/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/FlowFileTransformProxy.java +++ b/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/FlowFileTransformProxy.java @@ -62,13 +62,18 @@ public void onTrigger(final ProcessContext context, final ProcessSession session try { final String relationshipName = result.getRelationship(); final Relationship relationship = new Relationship.Builder().name(relationshipName).build(); + final Map attributes = result.getAttributes(); + if (REL_FAILURE.getName().equals(relationshipName)) { session.remove(transformed); + if (attributes != null) { + original = session.putAllAttributes(original, attributes); + } + session.transfer(original, REL_FAILURE); return; } - final Map attributes = result.getAttributes(); if (attributes != null) { transformed = session.putAllAttributes(transformed, attributes); } diff --git a/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/RecordTransformProxy.java b/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/RecordTransformProxy.java index 00389f5b6129..91d774e130b2 100644 --- a/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/RecordTransformProxy.java +++ b/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-bridge/src/main/java/org/apache/nifi/python/processor/RecordTransformProxy.java @@ -17,6 +17,7 @@ package org.apache.nifi.python.processor; +import org.apache.commons.io.IOUtils; import org.apache.nifi.NullSuppression; import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; @@ -264,13 +265,16 @@ private void writeResult(final RecordTransformResult result, final Map originalAttributes = originalFlowFile.getAttributes(); final RecordSchema writeSchema = writerFactory.getSchema(originalAttributes, transformed.getSchema()); writer = writerFactory.createWriter(getLogger(), writeSchema, out, originalAttributes); writer.beginRecordSet(); } catch (final Exception e) { + // If we failed to create the RecordSetWriter, ensure that we close the Output Stream + IOUtils.closeQuietly(out); session.remove(destinationFlowFile); throw e; } diff --git a/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-integration-tests/src/test/java/org.apache.nifi.py4j/PythonControllerInteractionIT.java b/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-integration-tests/src/test/java/org.apache.nifi.py4j/PythonControllerInteractionIT.java index 1303b98ac9ac..f84ad13b2089 100644 --- a/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-integration-tests/src/test/java/org.apache.nifi.py4j/PythonControllerInteractionIT.java +++ b/nifi-extension-bundles/nifi-py4j-bundle/nifi-py4j-integration-tests/src/test/java/org.apache.nifi.py4j/PythonControllerInteractionIT.java @@ -30,10 +30,6 @@ import org.apache.nifi.python.PythonProcessConfig; import org.apache.nifi.python.PythonProcessorDetails; import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.serialization.SimpleRecordSchema; -import org.apache.nifi.serialization.record.RecordField; -import org.apache.nifi.serialization.record.RecordFieldType; -import org.apache.nifi.serialization.record.RecordSchema; import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; @@ -52,7 +48,6 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.time.Duration; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -559,16 +554,18 @@ public void testProcessRestarted() { runner.assertTransferCount("success", 7); } - private RecordSchema createSimpleRecordSchema(final List fieldNames) { - final List recordFields = new ArrayList<>(); - for (final String fieldName : fieldNames) { - recordFields.add(new RecordField(fieldName, RecordFieldType.STRING.getDataType(), true)); - } - final RecordSchema schema = new SimpleRecordSchema(recordFields); - return schema; - } + @Test + public void testRouteToFailureWithAttributes() { + final TestRunner runner = createFlowFileTransform("FailWithAttributes"); + runner.enqueue("Hello, World"); + runner.run(); + runner.assertAllFlowFilesTransferred("failure", 1); + final MockFlowFile out = runner.getFlowFilesForRelationship("failure").getFirst(); + out.assertAttributeEquals("number", "1"); + out.assertAttributeEquals("failureReason", "Intentional failure of unit test"); + } public interface StringLookupService extends ControllerService { Optional lookup(Map coordinates); diff --git a/nifi-extension-bundles/nifi-py4j-bundle/nifi-python-test-extensions/src/main/resources/extensions/FailWithAttributes.py b/nifi-extension-bundles/nifi-py4j-bundle/nifi-python-test-extensions/src/main/resources/extensions/FailWithAttributes.py new file mode 100644 index 000000000000..3ea1afab5bbf --- /dev/null +++ b/nifi-extension-bundles/nifi-py4j-bundle/nifi-python-test-extensions/src/main/resources/extensions/FailWithAttributes.py @@ -0,0 +1,29 @@ +# 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. + +from nifiapi.flowfiletransform import FlowFileTransform, FlowFileTransformResult + +class FailWithAttributes(FlowFileTransform): + class Java: + implements = ['org.apache.nifi.python.processor.FlowFileTransform'] + class ProcessorDetails: + version = '0.0.1-SNAPSHOT' + description = 'Routes a FlowFile to failure and adds attributes to it.' + + def __init__(self, **kwargs): + pass + + def transform(self, context, flowFile): + return FlowFileTransformResult(relationship="failure", attributes={"number": "1", "failureReason": "Intentional failure of unit test"}) diff --git a/nifi-extension-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml b/nifi-extension-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml index a577a77b433f..cee35376df49 100644 --- a/nifi-extension-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml +++ b/nifi-extension-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml @@ -30,7 +30,7 @@ com.slack.api bolt-socket-mode - 1.39.3 + 1.40.0 diff --git a/nifi-extension-bundles/nifi-standard-shared-bundle/nifi-standard-shared-bom/pom.xml b/nifi-extension-bundles/nifi-standard-shared-bundle/nifi-standard-shared-bom/pom.xml index 8c00d25cc50a..4503d2e3a7cb 100644 --- a/nifi-extension-bundles/nifi-standard-shared-bundle/nifi-standard-shared-bom/pom.xml +++ b/nifi-extension-bundles/nifi-standard-shared-bundle/nifi-standard-shared-bom/pom.xml @@ -121,7 +121,7 @@ org.checkerframework checker-qual - 3.43.0 + 3.44.0 provided diff --git a/nifi-framework-bundle/pom.xml b/nifi-framework-bundle/pom.xml index b58bdc616589..b6b6de056cd6 100644 --- a/nifi-framework-bundle/pom.xml +++ b/nifi-framework-bundle/pom.xml @@ -317,7 +317,7 @@ io.dropwizard.metrics metrics-core - 4.2.25 + 4.2.26 org.apache.curator diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/auth.interceptor.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/auth.interceptor.ts index 7b003d886d0f..d7b64a43c9ae 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/auth.interceptor.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/auth.interceptor.ts @@ -17,7 +17,7 @@ import { inject } from '@angular/core'; import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; -import { catchError, map, take, combineLatest, tap } from 'rxjs'; +import { catchError, take, combineLatest, tap, NEVER, switchMap } from 'rxjs'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../state'; import { fullScreenError, setRoutedToFullScreenError } from '../../state/error/error.actions'; @@ -43,7 +43,7 @@ export const authInterceptor: HttpInterceptorFn = (request: HttpRequest tap(() => store.dispatch(setRoutedToFullScreenError({ routedToFullScreenError: true }))) ) ]).pipe( - map(([currentUserState, loginConfiguration, routedToFullScreenError]) => { + switchMap(([currentUserState, loginConfiguration, routedToFullScreenError]) => { if ( currentUserState.status === 'pending' && loginConfiguration?.loginSupported && @@ -61,7 +61,7 @@ export const authInterceptor: HttpInterceptorFn = (request: HttpRequest ); } - throw errorResponse; + return NEVER; }) ); } else { diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/polling.interceptor.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/polling.interceptor.ts index c444c2caa0f1..9862a3bc119f 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/polling.interceptor.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/interceptors/polling.interceptor.ts @@ -17,24 +17,36 @@ import { inject } from '@angular/core'; import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; -import { tap } from 'rxjs'; +import { catchError, NEVER } from 'rxjs'; import { NiFiState } from '../../state'; import { Store } from '@ngrx/store'; import { stopCurrentUserPolling } from '../../state/current-user/current-user.actions'; import { stopProcessGroupPolling } from '../../pages/flow-designer/state/flow/flow.actions'; import { stopClusterSummaryPolling } from '../../state/cluster-summary/cluster-summary.actions'; +import { fullScreenError } from '../../state/error/error.actions'; export const pollingInterceptor: HttpInterceptorFn = (request: HttpRequest, next: HttpHandlerFn) => { const store: Store = inject(Store); return next(request).pipe( - tap({ - error: (error) => { - if (error instanceof HttpErrorResponse && error.status === 0) { - store.dispatch(stopCurrentUserPolling()); - store.dispatch(stopProcessGroupPolling()); - store.dispatch(stopClusterSummaryPolling()); - } + catchError((errorResponse) => { + if (errorResponse instanceof HttpErrorResponse && errorResponse.status === 0) { + store.dispatch(stopCurrentUserPolling()); + store.dispatch(stopProcessGroupPolling()); + store.dispatch(stopClusterSummaryPolling()); + + store.dispatch( + fullScreenError({ + errorDetail: { + title: 'Unable to communicate with NiFi', + message: 'Please ensure the application is running and check the logs for any errors.' + } + }) + ); + + return NEVER; + } else { + throw errorResponse; } }) ); diff --git a/nifi-registry/pom.xml b/nifi-registry/pom.xml index e51e3ffe5667..45389dd8416a 100644 --- a/nifi-registry/pom.xml +++ b/nifi-registry/pom.xml @@ -36,10 +36,10 @@ 3.3.0 - 10.13.0 + 10.15.0 10.0.0 3.12.0 - 6.9.0.202403050737-r + 6.10.0.202406032230-r 2.12.1 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 7c6f73f2a7e9..b58e760cbd33 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 @@ protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String 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 cf2e190fee42..8e62196f6e0b 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.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.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 void deleteReportingTasks() throws NiFiClientException, IOException { } } + 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 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 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 ruleIdsOfInterest) throws NiFiClientException, IOException { + final long maxTimestamp = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(2L); + + while (System.currentTimeMillis() < maxTimestamp) { + final List 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 getFlowAnalysisRulesNotInState(final String desiredState, final Collection 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 List verifyReportingTaskConfig(final String return results.getRequest().getResults(); } + public List verifyFlowAnalysisRuleConfig(final String ruleId, final Map 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 e816c2d0000f..57e2dbe0c607 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 @@ protected void destroyFlow() throws NiFiClientException, IOException, Interrupte 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 000000000000..21b7a80a16f9 --- /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 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 properties = updatedComponent.getProperties(); + assertNotSame(SENSITIVE_PROPERTY_VALUE, properties.get(SENSITIVE_PROPERTY_NAME)); + + final Map 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 000000000000..b79b58526b60 --- /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 properties = new HashMap<>(); + properties.put("Successful Verification", "true"); + properties.put("Failure Node Number", "2"); + getClientUtil().updateFlowAnalysisRuleProperties(rule, properties); + + final List 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 000000000000..74f08f84fbbc --- /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 properties = Collections.singletonMap("Successful Verification", "true"); + getClientUtil().updateFlowAnalysisRuleProperties(rule, properties); + + final List 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 properties = Collections.singletonMap("Successful Verification", "true"); + getClientUtil().updateFlowAnalysisRuleProperties(rule, properties); + + // Verify with properties that will give us failed verification + final Map invalidProperties = Collections.singletonMap("Successful Verification", "false"); + final List 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 invalidProperties = Collections.singletonMap("Successful Verification", "foo"); + getClientUtil().updateFlowAnalysisRuleProperties(rule, invalidProperties); + + final Map validProperties = Collections.singletonMap("Successful Verification", "true"); + final List 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 invalidProperties = Collections.singletonMap("Successful Verification", "foo"); + getClientUtil().updateFlowAnalysisRuleProperties(rule, invalidProperties); + + final Map otherInvalidProperties = Collections.singletonMap("Successful Verification", "bar"); + final List 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 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 properties = new HashMap<>(); + properties.put("Successful Verification", "true"); + properties.put("Exception on Verification", "true"); + getClientUtil().updateFlowAnalysisRuleProperties(rule, properties); + + final List 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 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 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 34b3cdce29ac..2d002abad48d 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 @@ 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 f621f4e6ed83..e3e4b086aba8 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.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 VersionedReportingTaskImportResponseEntity importReportingTasks(Versioned }); } + @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 900ed791cde7..21fbe51b850e 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 21fc6779ac6c..a5af68399595 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.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 @@ protected List createCommands() { 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 000000000000..e87ae017c83d --- /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 { + + 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 000000000000..20ca24c00021 --- /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 { + + 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 000000000000..ab152abb650b --- /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 { + + 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 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 000000000000..7ee2d8ae04a6 --- /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 { + + 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 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 000000000000..8279789e9b59 --- /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 { + + 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 000000000000..aa9e2734d657 --- /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 { + + 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 000000000000..f97b41f415f4 --- /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 { + + 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 000000000000..02e16d97fdd9 --- /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 { + + 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 ruleEntities = flowAnalysisRulesEntity.getFlowAnalysisRules(); + if (ruleEntities == null) { + return; + } + + final List 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; + } +} diff --git a/pom.xml b/pom.xml index e87b8f7a8709..641e6fe9b644 100644 --- a/pom.xml +++ b/pom.xml @@ -110,8 +110,8 @@ UTF-8 UTF-8 2014 - 1.12.733 - 2.25.63 + 1.12.742 + 2.26.1 2.10.1 6.13.0 1.9.24 @@ -121,9 +121,9 @@ 1.17.0 1.26.2 1.5.6-3 - 2.10.1 + 2.11.0 3.14.0 - 3.11.0 + 3.11.1 2.16.1 1.12.0 4.5.14 @@ -133,7 +133,7 @@ 2.0.13 2.9.0 10.17.1.0 - 12.0.9 + 12.0.10 2.17.1 1.11.3 4.0.5 @@ -153,9 +153,9 @@ 5.12.0 3.10.6.Final 2.2 - 4.1.110.Final + 4.1.111.Final 6.0.0 - 6.1.8 + 6.1.9 6.3.0 2.2.22 2.2.224