diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd78684 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +target +.idea +.project +.settings +.classpath +.DS_Store +.metadata +.recommenders diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5b03111 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Copyright 2025 Snowflake Inc. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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 maven:3.9.9-eclipse-temurin-21 as builder + +COPY flow-diff /flow-diff +RUN mvn -f /flow-diff/pom.xml clean package + +FROM eclipse-temurin:21-jre-noble + +COPY entrypoint.sh /entrypoint.sh +COPY --from=builder /flow-diff/target/flow-diff.jar /flow-diff.jar + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..66a27ec --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4ab80e --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# ![https://www.snowflake.com/](https://www.snowflake.com/wp-content/uploads/2023/02/Snowflake-Logo-e1682724848599.png) Snowflake Flow Diff for Apache NiFi + +This action is brought to you by [Snowflake](https://www.snowflake.com/), don't hesitate to visit our website and reach out to us if you have questions about this action. + +## Usage + +When using the GitHub Flow Registry Client in NiFi to version control your flows, add the below file `.github/workflows/flowdiff.yml` to the repository into which flow definitions are versioned. + +Whenever a pull request is opened, reopened or when a new commit is pushed to an existing pull request, this workflow will be triggered and will compare the modified flow in the pull request and will +automatically comment the pull request with a human readable description of the changes included in the pull request. + +```yaml +name: Snowflake Flow Diff on Pull Requests +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + execute_flow_diff: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + name: Executing Flow Diff + steps: + # checking out the code of the pull request (merge commit - if the PR is mergeable) + - name: Checkout PR code + uses: actions/checkout@v4 + with: + path: submitted-changes + + # getting the path of the flow definition that changed (only one expected for now) + - id: files + uses: Ana06/get-changed-files@v1.2 + + # checking out the code without the change of the PR + - name: Checkout original code + uses: actions/checkout@v4 + with: + fetch-depth: 2 + path: original-code + - run: cd original-code && git checkout HEAD^ + + # Running the diff + - name: Snowflake Flow Diff + uses: snowflake-labs/snowflake-flow-diff@v0 + id: flowdiff + with: + flowA: 'original-code/${{ steps.files.outputs.all }}' + flowB: 'submitted-changes/${{ steps.files.outputs.all }}' +``` + +## Example + +The GitHub Action will automatically publish a comment on the pull request with a comprehensive description of the changes between the flows of the two branches. +Here is an example of what the comment could look like: + +```markdown +### Executing Snowflake Flow Diff for flow: `MyExample` + +- The destination of a connection has changed from `UpdateAttribute` to `InvokeHTTP` +- A self-loop connection `[success]` has been added on `UpdateAttribute` +- A connection `[success]` from `My Generate FlowFile Processor` to `UpdateAttribute` has been added +- A Processor has been renamed from `GenerateFlowFile` to `My Generate FlowFile Processor` +- In processor named `GenerateFlowFile`, the Scheduling Strategy changed from `TIMER_DRIVEN` to `CRON_DRIVEN` +- A Parameter Context named `Test Parameter Context` has been added +- The parameter context `Test Parameter Context` with parameters `{addedParam=newValue}` has been added to the process group `TestingFlowDiff` +- A Processor named `UpdateAttribute` has been removed +- The bundle `org.apache.nifi:nifi-standard-nar` has been changed from version `2.1.0` to version `2.2.0` +- In processor `GenerateFlowFile`, the Run Schedule changed from `1 min` to `* * * * * ?` +- A Parameter Context named `Another one to delete` has been added +- A Processor `UpdateAttribute` has been added with the below configuration + - `Store State` = `Do not store state` + - `canonical-value-lookup-cache-size` = `100` +``` diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..e96b3cb --- /dev/null +++ b/action.yml @@ -0,0 +1,49 @@ +# Copyright 2025 Snowflake Inc. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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. + +name: 'Snowflake Flow Diff' +description: 'Compare differences in Apache NiFi Flow Definitions' +author: 'Snowflake Inc.' +branding: + icon: git-pull-request + color: blue +inputs: + flowA: + description: 'Original Flow' + required: true + flowB: + description: 'New Version of the Flow' + required: true + token: + description: 'GITHUB_TOKEN or a repository-scoped PAT' + default: ${{ github.token }} + required: true + repository: + description: 'The full name of the repository in which to create or update a comment' + default: ${{ github.repository }} + required: true + issuenumber: + description: 'The number of the issue or pull request in which to create a comment' + required: true + default: ${{ github.event.number }} +runs: + using: 'docker' + image: 'Dockerfile' + args: + - ${{ inputs.flowA }} + - ${{ inputs.flowB }} + - ${{ inputs.token }} + - ${{ inputs.repository }} + - ${{ inputs.issuenumber }} diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..31737ef --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh -l + +# Copyright 2025 Snowflake Inc. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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. + +java -jar /flow-diff.jar $1 $2 >> /github/workspace/diff.txt + +OUTPUT=$(cat /github/workspace/diff.txt | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') + +curl -X POST \ + -H "Authorization: Token $3" \ + -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/$4/issues/$5/comments \ + -d "{\"body\":\"$OUTPUT\"}" diff --git a/flow-diff/pom.xml b/flow-diff/pom.xml new file mode 100644 index 0000000..d56b5ab --- /dev/null +++ b/flow-diff/pom.xml @@ -0,0 +1,136 @@ + + 4.0.0 + + com.snowflake.openflow + flow-diff + 0.0.1 + + Snowflake Flow Diff GitHub Action + + GitHub Action so that whenever a pull request is opened, reopened or when a new commit is + pushed to an existing pull request, a workflow will be triggered and will compare the + modified flow in the pull request and will automatically comment the pull request with a + human-readable description of the changes included in the pull request. + + https://github.com/snowflake-labs/snowflake-flow-diff + 2025 + + Snowflake Inc. + https://www.snowflake.com/ + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + + + UTF-8 + UTF-8 + 21 + 2.0.0 + 2.2.0 + 2.0.16 + 2.18.2 + 5.11.4 + + + + + org.apache.nifi + nifi-api + ${nifi-api.version} + + + org.apache.nifi + nifi-framework-core-api + ${nifi-framework.version} + + + org.apache.nifi + nifi-site-to-site-client + + + org.apache.nifi + nifi-expression-language + + + org.apache.nifi + nifi-framework-authorization + + + org.apache.nifi + c2-protocol-component-api + + + io.swagger.core.v3 + swagger-annotations + + + jakarta.xml.bind + jakarta.xml.bind-api + + + + + org.apache.nifi.registry + nifi-registry-flow-diff + ${nifi-framework.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + org.slf4j + slf4j-nop + ${slf4j.version} + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + com.snowflake.openflow.FlowDiff + + + false + + + + + + + diff --git a/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java b/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java new file mode 100644 index 0000000..a167d4b --- /dev/null +++ b/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java @@ -0,0 +1,454 @@ +/* + * Copyright 2025 Snowflake Inc. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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 com.snowflake.openflow; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.nifi.flow.Bundle; +import org.apache.nifi.flow.ComponentType; +import org.apache.nifi.flow.ConnectableComponent; +import org.apache.nifi.flow.VersionedComponent; +import org.apache.nifi.flow.VersionedConnection; +import org.apache.nifi.flow.VersionedControllerService; +import org.apache.nifi.flow.VersionedParameter; +import org.apache.nifi.flow.VersionedParameterContext; +import org.apache.nifi.flow.VersionedProcessGroup; +import org.apache.nifi.flow.VersionedProcessor; +import org.apache.nifi.registry.flow.FlowSnapshotContainer; +import org.apache.nifi.registry.flow.RegisteredFlowSnapshot; +import org.apache.nifi.registry.flow.diff.ConciseEvolvingDifferenceDescriptor; +import org.apache.nifi.registry.flow.diff.FlowComparator; +import org.apache.nifi.registry.flow.diff.FlowComparatorVersionedStrategy; +import org.apache.nifi.registry.flow.diff.FlowDifference; +import org.apache.nifi.registry.flow.diff.StandardComparableDataFlow; +import org.apache.nifi.registry.flow.diff.StandardFlowComparator; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Function; + +public class FlowDiff { + + private static String flowName; + private static Map parameterContexts; + + public static void main(String[] args) throws IOException { + + String pathA = args[0]; + String pathB = args[1]; + + final Set diffs = getDiff(pathA, pathB); + + System.out.println("> [!NOTE]"); + System.out.println("> This GitHub Action is created and maintained by [Snowflake](https://www.snowflake.com/)."); + System.out.println("### Executing Datavolo Flow Diff for flow: `" + flowName + "`"); + + for(FlowDifference diff : diffs) { + + switch (diff.getDifferenceType()) { + case COMPONENT_ADDED: { + if (diff.getComponentB().getComponentType().equals(ComponentType.FUNNEL)) { + System.out.println("- A Funnel has been added"); + } else if (diff.getComponentB().getComponentType().equals(ComponentType.CONNECTION)) { + final VersionedConnection connection = (VersionedConnection) diff.getComponentB(); + if (connection.getSource().getId().equals(connection.getDestination().getId())) { + System.out.println("- A self-loop connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` has been added on `" + connection.getSource().getName() + "`"); + } else { + System.out.println("- A connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` has been added"); + } + } else if (diff.getComponentB().getComponentType().equals(ComponentType.PROCESSOR)) { + final VersionedProcessor proc = (VersionedProcessor) diff.getComponentB(); + System.out.println("- A Processor" + + (isEmpty(diff.getComponentB().getName()) ? "" : " `" + diff.getComponentB().getName() + "`") + + " has been added with the configuration [" + printProcessorConf(proc) + "] and the below properties:"); + printProcessorProperties(proc); + } else if (diff.getComponentB().getComponentType().equals(ComponentType.CONTROLLER_SERVICE)) { + final VersionedControllerService cs = (VersionedControllerService) diff.getComponentB(); + System.out.println("- A Controller Service" + + (isEmpty(diff.getComponentB().getName()) ? "" : " `" + diff.getComponentB().getName() + "`") + + " has been added with the below properties:"); + printControllerProperties(cs); + } else { + System.out.println("- A " + diff.getComponentB().getComponentType().getTypeName() + + (isEmpty(diff.getComponentB().getName()) ? "" : " named `" + diff.getComponentB().getName() + "`") + + " has been added"); + } + break; + } + case COMPONENT_REMOVED: { + if (diff.getComponentA().getComponentType().equals(ComponentType.FUNNEL)) { + System.out.println("- A Funnel has been removed"); + } else { + System.out.println("- A " + diff.getComponentA().getComponentType().getTypeName() + + (isEmpty(diff.getComponentA().getName()) ? "" : " named `" + diff.getComponentA().getName() + "`") + + " has been removed"); + } + break; + } + case DESTINATION_CHANGED: { + System.out.println("- The destination of a connection has changed from `" + ((ConnectableComponent) diff.getValueA()).getName() + + "` to `" + ((ConnectableComponent) diff.getValueB()).getName() + "`"); + break; + } + case PROPERTY_CHANGED: { + System.out.println("- In the " + diff.getComponentA().getComponentType().getTypeName() + + " named `" + diff.getComponentA().getName() + "`, the value of the property " + + "`" + diff.getFieldName().get() + "` changed from `" + diff.getValueA() + + "` to `" + diff.getValueB() + "`"); + break; + } + case CONCURRENT_TASKS_CHANGED: { + System.out.println("- In processor `" + diff.getComponentA().getName() + "`, the number of concurrent tasks has been " + + ((int) diff.getValueA() > (int) diff.getValueB() ? "decreased" : "increased") + + " from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case BACKPRESSURE_DATA_SIZE_THRESHOLD_CHANGED: { + final VersionedConnection connection = (VersionedConnection) diff.getComponentA(); + System.out.println("- The data size backpressure threshold for the connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case BACKPRESSURE_OBJECT_THRESHOLD_CHANGED: { + final VersionedConnection connection = (VersionedConnection) diff.getComponentA(); + System.out.println("- The flowfile number backpressure threshold for the connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case BULLETIN_LEVEL_CHANGED: { + System.out.println("- In " + diff.getComponentA().getComponentType() + " named `" + diff.getComponentA().getName() + + "`, the bulletin level has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case RUN_DURATION_CHANGED: { + final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA(); + System.out.println("- In processor `" + processor.getName() + + "`, the Run Duration changed from " + + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case RUN_SCHEDULE_CHANGED: { + final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA(); + System.out.println("- In processor `" + processor.getName() + + "`, the Run Schedule changed from " + + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case AUTO_TERMINATED_RELATIONSHIPS_CHANGED: { + final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA(); + System.out.println("- In processor `" + processor.getName() + + "`, the list of auto-terminated relationships changed from " + + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case LOAD_BALANCE_STRATEGY_CHANGED: { + final VersionedConnection connection = (VersionedConnection) diff.getComponentA(); + System.out.println("- The load balancing strategy for the connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case LOAD_BALANCE_COMPRESSION_CHANGED: { + final VersionedConnection connection = (VersionedConnection) diff.getComponentA(); + System.out.println("- The load balancing compression for the connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case FLOWFILE_EXPIRATION_CHANGED: { + final VersionedConnection connection = (VersionedConnection) diff.getComponentA(); + System.out.println("- The flow file expiration for the connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` has been changed from `" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case PENALTY_DURATION_CHANGED: { + final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA(); + System.out.println("- In processor `" + processor.getName() + + "`, the penalty duration changed from " + + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case PARAMETER_CONTEXT_CHANGED: { + final VersionedProcessGroup pg = (VersionedProcessGroup) diff.getComponentB(); + System.out.println("- The parameter context `" + pg.getParameterContextName() + "` with parameters `" + + printParameterContext(parameterContexts.get(pg.getParameterContextName())) + + "` has been added to the process group `" + pg.getName() + "`"); + break; + } + case POSITION_CHANGED: { + System.out.println("- A " + diff.getComponentA().getComponentType() + + (isEmpty(diff.getComponentA().getName()) ? "" : " named `" + diff.getComponentA().getName() + "`") + + " has been moved to another position"); + break; + } + case SCHEDULING_STRATEGY_CHANGED: { + final VersionedProcessor processor = (VersionedProcessor) diff.getComponentA(); + System.out.println("- In processor named `" + processor.getName() + + "`, the Scheduling Strategy changed from " + + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case BUNDLE_CHANGED: + Bundle before = (Bundle) diff.getValueA(); + Bundle after = (Bundle) diff.getValueB(); + System.out.println("- The bundle `" + + before.getGroup() + ":" + before.getArtifact() + + "` has been changed from version " + + "`" + before.getVersion() + "` to version `" + after.getVersion() + "`"); + break; + case NAME_CHANGED: { + System.out.println("- A " + + diff.getComponentA().getComponentType() + + " has been renamed from " + + "`" + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case PROPERTY_ADDED: { + final String propKey = (String) diff.getValueB(); + String propValue = null; + if (diff.getComponentB() instanceof VersionedProcessor) { + if (((VersionedProcessor) diff.getComponentB()).getPropertyDescriptors().get(propKey).isSensitive()) { + propValue = ""; + } else { + propValue = ((VersionedProcessor) diff.getComponentB()).getProperties().get(propKey); + } + } + if (diff.getComponentB() instanceof VersionedControllerService) { + if (((VersionedControllerService) diff.getComponentB()).getPropertyDescriptors().get(propKey).isSensitive()) { + propValue = ""; + } else { + propValue = ((VersionedControllerService) diff.getComponentB()).getProperties().get(propKey); + } + } + System.out.println("- In " + diff.getComponentA().getComponentType() + + " named `" + diff.getComponentA().getName() + "`, a property has been added: " + + "`" + propKey + "` = `" + propValue + "`"); + break; + } + case PROPERTY_PARAMETERIZED: { + final String propKey = diff.getFieldName().get(); + String propValue = null; + if (diff.getComponentB() instanceof VersionedProcessor) { + propValue = ((VersionedProcessor) diff.getComponentB()).getProperties().get(propKey); + } + if (diff.getComponentB() instanceof VersionedControllerService) { + propValue = ((VersionedControllerService) diff.getComponentB()).getProperties().get(propKey); + } + System.out.println("- In " + diff.getComponentA().getComponentType() + + " named `" + diff.getComponentA().getName() + "`, a property is now referencing a parameter: " + + "`" + propKey + "` = `" + propValue + "`"); + break; + } + case PROPERTY_PARAMETERIZATION_REMOVED: { + final String propKey = diff.getFieldName().get(); + System.out.println("- In " + diff.getComponentA().getComponentType() + + " named `" + diff.getComponentA().getName() + + "`, the property `" + propKey + "` is no longer referencing a parameter"); + break; + } + case SCHEDULED_STATE_CHANGED: { + System.out.println("- In the " + diff.getComponentA().getComponentType().getTypeName() + + " named `" + diff.getComponentA().getName() + + "`, the Schedule State changed from `" + + diff.getValueA() + "` to `" + diff.getValueB() + "`"); + break; + } + case PARAMETER_ADDED: { + final String paramKey = diff.getFieldName().get(); + final VersionedParameterContext pc = (VersionedParameterContext) diff.getComponentB(); + final VersionedParameter param = pc.getParameters().stream().filter(p -> p.getName().equals(paramKey)).findFirst().get(); + System.out.println("- In the Parameter Context `" + pc.getName() + "` a parameter has been added: `" + + paramKey + "` = `" + (param.isSensitive() ? "" : param.getValue()) + "`"); + break; + } + case PARAMETER_REMOVED: { + final String paramKey = diff.getFieldName().get(); + final VersionedParameterContext pc = (VersionedParameterContext) diff.getComponentB(); + System.out.println("- In the Parameter Context `"+ pc.getName() + "` the parameter `" + paramKey + "` has been removed"); + break; + } + case PROPERTY_REMOVED: { + final String propKey = diff.getFieldName().get(); + System.out.println("- In " + + diff.getComponentA().getComponentType() + + " named `" + diff.getComponentA().getName() + + "`, the property `" + propKey + "` has been removed"); + break; + } + case PARAMETER_VALUE_CHANGED: { + final String paramKey = diff.getFieldName().get(); + final VersionedParameterContext pcBefore = (VersionedParameterContext) diff.getComponentA(); + final VersionedParameterContext pcAfter = (VersionedParameterContext) diff.getComponentB(); + final VersionedParameter paramBefore = pcBefore.getParameters().stream().filter(p -> p.getName().equals(paramKey)).findFirst().get(); + final VersionedParameter paramAfter = pcAfter.getParameters().stream().filter(p -> p.getName().equals(paramKey)).findFirst().get(); + System.out.println("- In the Parameter Context `" + pcAfter.getName() + + "`, the value of the parameter `" + paramKey + "` has changed from `" + + (paramBefore.isSensitive() ? "" : paramBefore.getValue()) + "`" + + " to `" + + (paramAfter.isSensitive() ? "" : paramAfter.getValue()) + "`"); + break; + } + case INHERITED_CONTEXTS_CHANGED: + final VersionedParameterContext pc = (VersionedParameterContext) diff.getComponentA(); + System.out.println("- In the Parameter Context `" + pc.getName() + + "`, the list of inherited parameter contexts changed from `" + + diff.getValueA() + "`" + " to `" + diff.getValueB() + "`"); + break; + case BENDPOINTS_CHANGED: + final VersionedConnection connection = (VersionedConnection) diff.getComponentA(); + System.out.println("- The bending points for the connection `" + + (isEmpty(connection.getName()) ? connection.getSelectedRelationships().toString() : connection.getName()) + + "` from `" + connection.getSource().getName() + "` to `" + connection.getDestination().getName() + + "` have been changed"); + break; + default: + System.out.println("- " + diff.getDescription() + " (" + diff.getDifferenceType() + ")"); + System.out.println(" - " + diff.getValueA()); + System.out.println(" - " + diff.getValueB()); + System.out.println(" - " + diff.getComponentA()); + System.out.println(" - " + diff.getComponentB()); + System.out.println(" - " + diff.getFieldName()); + break; + } + } + } + + public static Set getDiff(final String pathA, final String pathB) throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + final JsonFactory factory = new JsonFactory(objectMapper); + final FlowSnapshotContainer snapshotA = getFlowContainer(pathA, factory); + final FlowSnapshotContainer snapshotB = getFlowContainer(pathB, factory); + + // identifier is null for parameter contexts, and we know that names are unique so setting name as id + snapshotA.getFlowSnapshot().getParameterContexts().values().forEach(pc -> pc.setIdentifier(pc.getName())); + snapshotB.getFlowSnapshot().getParameterContexts().values().forEach(pc -> pc.setIdentifier(pc.getName())); + + final FlowComparator flowComparator = new StandardFlowComparator( + new StandardComparableDataFlow( + "Flow A", + snapshotA.getFlowSnapshot().getFlowContents(), + null, + null, + null, + new HashSet<>(snapshotA.getFlowSnapshot().getParameterContexts().values()), + null, + null + ), + new StandardComparableDataFlow( + "Flow B", + snapshotB.getFlowSnapshot().getFlowContents(), + null, + null, + null, + new HashSet<>(snapshotB.getFlowSnapshot().getParameterContexts().values()), + null, + null + ), + Collections.emptySet(), + new ConciseEvolvingDifferenceDescriptor(), + Function.identity(), + VersionedComponent::getIdentifier, + FlowComparatorVersionedStrategy.DEEP + ); + + flowName = snapshotA.getFlowSnapshot().getFlow().getName(); + parameterContexts = snapshotB.getFlowSnapshot().getParameterContexts(); + + final SortedSet sortedDiffs = new TreeSet(new Comparator() { + @Override + public int compare(FlowDifference o1, FlowDifference o2) { + String id1 = o1.getComponentA() == null ? String.valueOf(o1.hashCode()) : o1.getComponentA().getInstanceIdentifier() + o1.hashCode(); + String id2 = o2.getComponentA() == null ? String.valueOf(o2.hashCode()) : o2.getComponentA().getInstanceIdentifier() + o2.hashCode(); + return (id1 == null ? String.valueOf(o1.hashCode()) : id1).compareTo(id2 == null ? String.valueOf(o2.hashCode()) : id2); + } + }); + sortedDiffs.addAll(flowComparator.compare().getDifferences()); + + return sortedDiffs; + } + + static FlowSnapshotContainer getFlowContainer(final String path, final JsonFactory factory) throws IOException { + final File snapshotFile = new File(path); + try (final JsonParser parser = factory.createParser(snapshotFile)) { + final RegisteredFlowSnapshot snapshot = parser.readValueAs(RegisteredFlowSnapshot.class); + return new FlowSnapshotContainer(snapshot); + } + } + + static String printParameterContext(final VersionedParameterContext pc) { + final Map parameters = new HashMap<>(); + for (VersionedParameter p : pc.getParameters()) { + if (p.isSensitive()) { + parameters.put(p.getName(), ""); + } else { + parameters.put(p.getName(), p.getValue()); + } + } + return parameters.toString(); + } + + static void printProcessorProperties(final VersionedProcessor proc) { + for (String key : proc.getProperties().keySet()) { + System.out.println(" - `" + key + "` = `" + proc.getProperties().get(key) + "`"); + } + } + + static String printProcessorConf(final VersionedProcessor proc) { + return "`" + proc.getExecutionNode() + "` nodes, `" + proc.getConcurrentlySchedulableTaskCount() + "` concurrent tasks, `" + + proc.getRunDurationMillis() + "ms` run duration, `" + proc.getBulletinLevel() + "` bulletin level, `" + + proc.getSchedulingStrategy() + "` (`" + proc.getSchedulingPeriod() + "`), `" + + proc.getPenaltyDuration() + "` penalty duration, `" + proc.getYieldDuration() + "` yield duration"; + } + + static void printControllerProperties(final VersionedControllerService cs) { + for (String key : cs.getProperties().keySet()) { + System.out.println(" - `" + key + "` = `" + cs.getProperties().get(key) + "`"); + } + } + + static boolean isEmpty(final String string) { + return string == null || string.isEmpty(); + } +} diff --git a/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java b/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java new file mode 100644 index 0000000..8867a22 --- /dev/null +++ b/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2025 Snowflake Inc. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed 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 com.snowflake.openflow; + +import org.apache.nifi.registry.flow.diff.DifferenceType; +import org.apache.nifi.registry.flow.diff.FlowDifference; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FlowDiffTest { + + @Test + void testDiffV1V2() throws IOException { + String flowV1 = "src/test/resources/flow_v1_initial.json"; + String flowV2 = "src/test/resources/flow_v2_added_component.json"; + Set diffs = FlowDiff.getDiff(flowV1, flowV2); + assertEquals(diffs.size(), 3); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.POSITION_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.COMPONENT_ADDED))); + } + + @Test + void testDiffV2V3() throws IOException { + String flowV2 = "src/test/resources/flow_v2_added_component.json"; + String flowV3 = "src/test/resources/flow_v3_config_changes.json"; + Set diffs = FlowDiff.getDiff(flowV2, flowV3); + assertEquals(diffs.size(), 13); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.RUN_SCHEDULE_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BACKPRESSURE_DATA_SIZE_THRESHOLD_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.FLOWFILE_EXPIRATION_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BULLETIN_LEVEL_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.CONCURRENT_TASKS_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PENALTY_DURATION_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_CONTEXT_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BACKPRESSURE_OBJECT_THRESHOLD_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.AUTO_TERMINATED_RELATIONSHIPS_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.LOAD_BALANCE_STRATEGY_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.LOAD_BALANCE_COMPRESSION_CHANGED))); + } + + @Test + void testDiffV3V4() throws IOException { + String flowV3 = "src/test/resources/flow_v3_config_changes.json"; + String flowV4 = "src/test/resources/flow_v4_parameters.json"; + Set diffs = FlowDiff.getDiff(flowV3, flowV4); + assertEquals(diffs.size(), 14); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.INHERITED_CONTEXTS_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_ADDED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.SCHEDULING_STRATEGY_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_PARAMETERIZED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.NAME_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.BUNDLE_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.COMPONENT_REMOVED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.RUN_SCHEDULE_CHANGED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_REMOVED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.DESTINATION_CHANGED))); + } + + @Test + void testDiffV4V5() throws IOException { + String flowV4 = "src/test/resources/flow_v4_parameters.json"; + String flowV5 = "src/test/resources/flow_v5_property_parameter.json"; + Set diffs = FlowDiff.getDiff(flowV4, flowV5); + assertEquals(diffs.size(), 7); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_PARAMETERIZATION_REMOVED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.SCHEDULED_STATE_CHANGED))); + } + + @Test + void testDiffV3V5() throws IOException { + String flowV3 = "src/test/resources/flow_v3_config_changes.json"; + String flowV5 = "src/test/resources/flow_v5_property_parameter.json"; + Set diffs = FlowDiff.getDiff(flowV3, flowV5); + assertEquals(diffs.size(), 13); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_ADDED))); + } + + @Test + void testDiffV5V6() throws IOException { + String flowV5 = "src/test/resources/flow_v5_property_parameter.json"; + String flowV6 = "src/test/resources/flow_v6_parameter_value.json"; + Set diffs = FlowDiff.getDiff(flowV5, flowV6); + assertEquals(diffs.size(), 3); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PROPERTY_REMOVED))); + assertTrue(diffs.stream().anyMatch(d -> d.getDifferenceType().equals(DifferenceType.PARAMETER_VALUE_CHANGED))); + } +} diff --git a/flow-diff/src/test/resources/flow_v1_initial.json b/flow-diff/src/test/resources/flow_v1_initial.json new file mode 100644 index 0000000..9647d47 --- /dev/null +++ b/flow-diff/src/test/resources/flow_v1_initial.json @@ -0,0 +1,233 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "test", + "identifier" : "test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "33eb1dae-38c6-3540-a286-7a364054cf4c", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "GenerateFlowFile", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "TestingFlowDiff", + "outputPorts" : [ ], + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ "success" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "33eb1dae-38c6-3540-a286-7a364054cf4c", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -288.0, + "y" : -24.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "maxBackoffPeriod" : "10 mins", + "name" : "GenerateFlowFile", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -286.0, + "y" : -250.0 + }, + "properties" : { + "character-set" : "UTF-8", + "File Size" : "0B", + "Batch Size" : "1", + "Unique FlowFiles" : "false", + "Data Format" : "Text" + }, + "propertyDescriptors" : { + "character-set" : { + "displayName" : "Character Set", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "character-set", + "sensitive" : false + }, + "File Size" : { + "displayName" : "File Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "File Size", + "sensitive" : false + }, + "mime-type" : { + "displayName" : "Mime Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "mime-type", + "sensitive" : false + }, + "generate-ff-custom-text" : { + "displayName" : "Custom Text", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "generate-ff-custom-text", + "sensitive" : false + }, + "Batch Size" : { + "displayName" : "Batch Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Batch Size", + "sensitive" : false + }, + "Unique FlowFiles" : { + "displayName" : "Unique FlowFiles", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Unique FlowFiles", + "sensitive" : false + }, + "Data Format" : { + "displayName" : "Data Format", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Data Format", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "1 min", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { }, + "parameterProviders" : { }, + "snapshotMetadata" : { + "author" : "pvillard@datavolo.io", + "flowIdentifier" : "test", + "timestamp" : 0 + } +} \ No newline at end of file diff --git a/flow-diff/src/test/resources/flow_v2_added_component.json b/flow-diff/src/test/resources/flow_v2_added_component.json new file mode 100644 index 0000000..e52cb9c --- /dev/null +++ b/flow-diff/src/test/resources/flow_v2_added_component.json @@ -0,0 +1,328 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "test", + "identifier" : "test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "GenerateFlowFile", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "33eb1dae-38c6-3540-a286-7a364054cf4c", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "GenerateFlowFile", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "TestingFlowDiff", + "outputPorts" : [ ], + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ "success" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "33eb1dae-38c6-3540-a286-7a364054cf4c", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -504.0, + "y" : 16.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "maxBackoffPeriod" : "10 mins", + "name" : "GenerateFlowFile", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -286.0, + "y" : -250.0 + }, + "properties" : { + "character-set" : "UTF-8", + "File Size" : "0B", + "Batch Size" : "1", + "Unique FlowFiles" : "false", + "Data Format" : "Text" + }, + "propertyDescriptors" : { + "character-set" : { + "displayName" : "Character Set", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "character-set", + "sensitive" : false + }, + "File Size" : { + "displayName" : "File Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "File Size", + "sensitive" : false + }, + "mime-type" : { + "displayName" : "Mime Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "mime-type", + "sensitive" : false + }, + "generate-ff-custom-text" : { + "displayName" : "Custom Text", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "generate-ff-custom-text", + "sensitive" : false + }, + "Batch Size" : { + "displayName" : "Batch Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Batch Size", + "sensitive" : false + }, + "Unique FlowFiles" : { + "displayName" : "Unique FlowFiles", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Unique FlowFiles", + "sensitive" : false + }, + "Data Format" : { + "displayName" : "Data Format", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Data Format", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "1 min", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ "success" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -16.0, + "y" : 24.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { }, + "parameterProviders" : { }, + "snapshotMetadata" : { + "author" : "pvillard@datavolo.io", + "flowIdentifier" : "test", + "timestamp" : 0 + } +} \ No newline at end of file diff --git a/flow-diff/src/test/resources/flow_v3_config_changes.json b/flow-diff/src/test/resources/flow_v3_config_changes.json new file mode 100644 index 0000000..4f6fba2 --- /dev/null +++ b/flow-diff/src/test/resources/flow_v3_config_changes.json @@ -0,0 +1,378 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "test", + "identifier" : "test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ { + "x" : 453.0, + "y" : 63.0 + }, { + "x" : 453.0, + "y" : 113.0 + } ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 MB", + "backPressureObjectThreshold" : 1000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "100 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b", + "labelIndex" : 0, + "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT", + "loadBalanceStrategy" : "ROUND_ROBIN", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "GenerateFlowFile", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "33eb1dae-38c6-3540-a286-7a364054cf4c", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "GenerateFlowFile", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "TestingFlowDiff", + "outputPorts" : [ ], + "parameterContextName" : "Test Parameter Context", + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ "success" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "33eb1dae-38c6-3540-a286-7a364054cf4c", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -504.0, + "y" : 16.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "maxBackoffPeriod" : "10 mins", + "name" : "GenerateFlowFile", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -286.0, + "y" : -250.0 + }, + "properties" : { + "character-set" : "UTF-8", + "File Size" : "0B", + "Batch Size" : "1", + "Unique FlowFiles" : "false", + "Data Format" : "Text" + }, + "propertyDescriptors" : { + "character-set" : { + "displayName" : "Character Set", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "character-set", + "sensitive" : false + }, + "File Size" : { + "displayName" : "File Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "File Size", + "sensitive" : false + }, + "mime-type" : { + "displayName" : "Mime Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "mime-type", + "sensitive" : false + }, + "generate-ff-custom-text" : { + "displayName" : "Custom Text", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "generate-ff-custom-text", + "sensitive" : false + }, + "Batch Size" : { + "displayName" : "Batch Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Batch Size", + "sensitive" : false + }, + "Unique FlowFiles" : { + "displayName" : "Unique FlowFiles", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Unique FlowFiles", + "sensitive" : false + }, + "Data Format" : { + "displayName" : "Data Format", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Data Format", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "1 min", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "ERROR", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.03-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 2, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "10 sec", + "position" : { + "x" : -16.0, + "y" : 24.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "10 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { + "Test Parameter Context" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "Test Parameter Context", + "parameters" : [ { + "description" : "", + "name" : "test", + "provided" : false, + "sensitive" : false, + "value" : "test" + } ] + } + }, + "parameterProviders" : { }, + "snapshotMetadata" : { + "author" : "pvillard@datavolo.io", + "flowIdentifier" : "test", + "timestamp" : 0 + } +} \ No newline at end of file diff --git a/flow-diff/src/test/resources/flow_v4_parameters.json b/flow-diff/src/test/resources/flow_v4_parameters.json new file mode 100644 index 0000000..7c2ed14 --- /dev/null +++ b/flow-diff/src/test/resources/flow_v4_parameters.json @@ -0,0 +1,638 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "test", + "identifier" : "test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ { + "x" : 453.0, + "y" : 63.0 + }, { + "x" : 453.0, + "y" : 113.0 + } ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 MB", + "backPressureObjectThreshold" : 1000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "100 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b", + "labelIndex" : 0, + "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT", + "loadBalanceStrategy" : "ROUND_ROBIN", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "My Generate FlowFile Processor", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c", + "name" : "InvokeHTTP", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "My Generate FlowFile Processor", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "TestingFlowDiff", + "outputPorts" : [ ], + "parameterContextName" : "Test Parameter Context", + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c", + "maxBackoffPeriod" : "10 mins", + "name" : "InvokeHTTP", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -496.0, + "y" : 24.0 + }, + "properties" : { + "Request Content-Encoding" : "DISABLED", + "Request Multipart Form-Data Filename Enabled" : "true", + "Request Chunked Transfer-Encoding Enabled" : "false", + "HTTP/2 Disabled" : "False", + "Connection Timeout" : "5 secs", + "Response Cookie Strategy" : "DISABLED", + "Socket Read Timeout" : "15 secs", + "Socket Idle Connections" : "5", + "Request Body Enabled" : "true", + "HTTP URL" : "${url}", + "Socket Idle Timeout" : "5 mins", + "Response Redirects Enabled" : "True", + "Socket Write Timeout" : "15 secs", + "Response FlowFile Naming Strategy" : "RANDOM", + "Response Cache Enabled" : "false", + "Request Date Header Enabled" : "True", + "Request Failure Penalization Enabled" : "false", + "Response Body Attribute Size" : "256", + "Response Generation Required" : "false", + "Response Header Request Attributes Enabled" : "false", + "HTTP Method" : "GET", + "Request Content-Type" : "${mime.type}", + "Request Digest Authentication Enabled" : "false", + "Response Cache Size" : "10MB", + "Response Body Ignored" : "false" + }, + "propertyDescriptors" : { + "Request Content-Encoding" : { + "displayName" : "Request Content-Encoding", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Content-Encoding", + "sensitive" : false + }, + "proxy-configuration-service" : { + "displayName" : "Proxy Configuration Service", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "proxy-configuration-service", + "sensitive" : false + }, + "Request Multipart Form-Data Filename Enabled" : { + "displayName" : "Request Multipart Form-Data Filename Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Multipart Form-Data Filename Enabled", + "sensitive" : false + }, + "Request Chunked Transfer-Encoding Enabled" : { + "displayName" : "Request Chunked Transfer-Encoding Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Chunked Transfer-Encoding Enabled", + "sensitive" : false + }, + "HTTP/2 Disabled" : { + "displayName" : "HTTP/2 Disabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP/2 Disabled", + "sensitive" : false + }, + "Connection Timeout" : { + "displayName" : "Connection Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Connection Timeout", + "sensitive" : false + }, + "Response Cookie Strategy" : { + "displayName" : "Response Cookie Strategy", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cookie Strategy", + "sensitive" : false + }, + "Request Password" : { + "displayName" : "Request Password", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Password", + "sensitive" : true + }, + "Socket Read Timeout" : { + "displayName" : "Socket Read Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Read Timeout", + "sensitive" : false + }, + "Socket Idle Connections" : { + "displayName" : "Socket Idle Connections", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Idle Connections", + "sensitive" : false + }, + "Request Body Enabled" : { + "displayName" : "Request Body Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Body Enabled", + "sensitive" : false + }, + "HTTP URL" : { + "displayName" : "HTTP URL", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP URL", + "sensitive" : false + }, + "Request OAuth2 Access Token Provider" : { + "displayName" : "Request OAuth2 Access Token Provider", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "Request OAuth2 Access Token Provider", + "sensitive" : false + }, + "Socket Idle Timeout" : { + "displayName" : "Socket Idle Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Idle Timeout", + "sensitive" : false + }, + "Response Redirects Enabled" : { + "displayName" : "Response Redirects Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Redirects Enabled", + "sensitive" : false + }, + "Socket Write Timeout" : { + "displayName" : "Socket Write Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Write Timeout", + "sensitive" : false + }, + "Request Header Attributes Pattern" : { + "displayName" : "Request Header Attributes Pattern", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Header Attributes Pattern", + "sensitive" : false + }, + "Response FlowFile Naming Strategy" : { + "displayName" : "Response FlowFile Naming Strategy", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response FlowFile Naming Strategy", + "sensitive" : false + }, + "Response Cache Enabled" : { + "displayName" : "Response Cache Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cache Enabled", + "sensitive" : false + }, + "Request Date Header Enabled" : { + "displayName" : "Request Date Header Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Date Header Enabled", + "sensitive" : false + }, + "Request Failure Penalization Enabled" : { + "displayName" : "Request Failure Penalization Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Failure Penalization Enabled", + "sensitive" : false + }, + "Response Body Attribute Size" : { + "displayName" : "Response Body Attribute Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Attribute Size", + "sensitive" : false + }, + "SSL Context Service" : { + "displayName" : "SSL Context Service", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "SSL Context Service", + "sensitive" : false + }, + "Response Generation Required" : { + "displayName" : "Response Generation Required", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Generation Required", + "sensitive" : false + }, + "Request User-Agent" : { + "displayName" : "Request User-Agent", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request User-Agent", + "sensitive" : false + }, + "Response Header Request Attributes Enabled" : { + "displayName" : "Response Header Request Attributes Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Header Request Attributes Enabled", + "sensitive" : false + }, + "HTTP Method" : { + "displayName" : "HTTP Method", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP Method", + "sensitive" : false + }, + "Request Username" : { + "displayName" : "Request Username", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Username", + "sensitive" : false + }, + "Request Content-Type" : { + "displayName" : "Request Content-Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Content-Type", + "sensitive" : false + }, + "Response Body Attribute Name" : { + "displayName" : "Response Body Attribute Name", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Attribute Name", + "sensitive" : false + }, + "Request Digest Authentication Enabled" : { + "displayName" : "Request Digest Authentication Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Digest Authentication Enabled", + "sensitive" : false + }, + "Request Multipart Form-Data Name" : { + "displayName" : "Request Multipart Form-Data Name", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Multipart Form-Data Name", + "sensitive" : false + }, + "Response Cache Size" : { + "displayName" : "Response Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cache Size", + "sensitive" : false + }, + "Response Body Ignored" : { + "displayName" : "Response Body Ignored", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Ignored", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.InvokeHTTP", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "maxBackoffPeriod" : "10 mins", + "name" : "My Generate FlowFile Processor", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -286.0, + "y" : -250.0 + }, + "properties" : { + "character-set" : "UTF-8", + "File Size" : "0B", + "test" : "#{new param}", + "Batch Size" : "1", + "custom" : "custom", + "Unique FlowFiles" : "false", + "Data Format" : "Text" + }, + "propertyDescriptors" : { + "character-set" : { + "displayName" : "Character Set", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "character-set", + "sensitive" : false + }, + "File Size" : { + "displayName" : "File Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "File Size", + "sensitive" : false + }, + "mime-type" : { + "displayName" : "Mime Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "mime-type", + "sensitive" : false + }, + "generate-ff-custom-text" : { + "displayName" : "Custom Text", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "generate-ff-custom-text", + "sensitive" : false + }, + "test" : { + "displayName" : "test", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "test", + "sensitive" : false + }, + "Batch Size" : { + "displayName" : "Batch Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Batch Size", + "sensitive" : false + }, + "custom" : { + "displayName" : "custom", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "custom", + "sensitive" : false + }, + "Unique FlowFiles" : { + "displayName" : "Unique FlowFiles", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Unique FlowFiles", + "sensitive" : false + }, + "Data Format" : { + "displayName" : "Data Format", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Data Format", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "* * * * * ?", + "schedulingStrategy" : "CRON_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "ERROR", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 2, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "10 sec", + "position" : { + "x" : -16.0, + "y" : 24.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "10 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { + "Another one to delete" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "Another one to delete", + "parameters" : [ { + "description" : "", + "name" : "secured", + "provided" : false, + "sensitive" : true + } ] + }, + "Test Parameter Context" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ "Another one to delete" ], + "name" : "Test Parameter Context", + "parameters" : [ { + "description" : "", + "name" : "new param", + "provided" : false, + "sensitive" : false, + "value" : "new value" + } ] + } + }, + "parameterProviders" : { }, + "snapshotMetadata" : { + "author" : "pvillard@datavolo.io", + "flowIdentifier" : "test", + "timestamp" : 0 + } +} \ No newline at end of file diff --git a/flow-diff/src/test/resources/flow_v5_property_parameter.json b/flow-diff/src/test/resources/flow_v5_property_parameter.json new file mode 100644 index 0000000..ba49fdf --- /dev/null +++ b/flow-diff/src/test/resources/flow_v5_property_parameter.json @@ -0,0 +1,651 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "test", + "identifier" : "test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ { + "x" : 453.0, + "y" : 63.0 + }, { + "x" : 453.0, + "y" : 113.0 + } ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 MB", + "backPressureObjectThreshold" : 1000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "100 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b", + "labelIndex" : 0, + "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT", + "loadBalanceStrategy" : "ROUND_ROBIN", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "My Generate FlowFile Processor", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c", + "name" : "InvokeHTTP", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "My Generate FlowFile Processor", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "TestingFlowDiff", + "outputPorts" : [ ], + "parameterContextName" : "Test Parameter Context", + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c", + "maxBackoffPeriod" : "10 mins", + "name" : "InvokeHTTP", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -496.0, + "y" : 24.0 + }, + "properties" : { + "Request Content-Encoding" : "DISABLED", + "nonSens" : "#{addedParam}", + "Request Multipart Form-Data Filename Enabled" : "true", + "Request Chunked Transfer-Encoding Enabled" : "false", + "HTTP/2 Disabled" : "False", + "Connection Timeout" : "5 secs", + "Response Cookie Strategy" : "DISABLED", + "Socket Read Timeout" : "15 secs", + "Socket Idle Connections" : "5", + "Request Body Enabled" : "true", + "sensitiveDynamicProp" : "#{newSensitiveParam}", + "HTTP URL" : "${url}", + "Socket Idle Timeout" : "5 mins", + "Response Redirects Enabled" : "True", + "Socket Write Timeout" : "15 secs", + "Response FlowFile Naming Strategy" : "RANDOM", + "Response Cache Enabled" : "false", + "Request Date Header Enabled" : "True", + "Request Failure Penalization Enabled" : "false", + "Response Body Attribute Size" : "256", + "Response Generation Required" : "false", + "Response Header Request Attributes Enabled" : "false", + "HTTP Method" : "GET", + "Request Content-Type" : "${mime.type}", + "Request Digest Authentication Enabled" : "false", + "Response Cache Size" : "10MB", + "Response Body Ignored" : "false" + }, + "propertyDescriptors" : { + "Request Content-Encoding" : { + "displayName" : "Request Content-Encoding", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Content-Encoding", + "sensitive" : false + }, + "proxy-configuration-service" : { + "displayName" : "Proxy Configuration Service", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "proxy-configuration-service", + "sensitive" : false + }, + "nonSens" : { + "displayName" : "nonSens", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "nonSens", + "sensitive" : false + }, + "Request Multipart Form-Data Filename Enabled" : { + "displayName" : "Request Multipart Form-Data Filename Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Multipart Form-Data Filename Enabled", + "sensitive" : false + }, + "Request Chunked Transfer-Encoding Enabled" : { + "displayName" : "Request Chunked Transfer-Encoding Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Chunked Transfer-Encoding Enabled", + "sensitive" : false + }, + "HTTP/2 Disabled" : { + "displayName" : "HTTP/2 Disabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP/2 Disabled", + "sensitive" : false + }, + "Connection Timeout" : { + "displayName" : "Connection Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Connection Timeout", + "sensitive" : false + }, + "Response Cookie Strategy" : { + "displayName" : "Response Cookie Strategy", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cookie Strategy", + "sensitive" : false + }, + "Request Password" : { + "displayName" : "Request Password", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Password", + "sensitive" : true + }, + "Socket Read Timeout" : { + "displayName" : "Socket Read Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Read Timeout", + "sensitive" : false + }, + "Socket Idle Connections" : { + "displayName" : "Socket Idle Connections", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Idle Connections", + "sensitive" : false + }, + "Request Body Enabled" : { + "displayName" : "Request Body Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Body Enabled", + "sensitive" : false + }, + "sensitiveDynamicProp" : { + "displayName" : "sensitiveDynamicProp", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "sensitiveDynamicProp", + "sensitive" : true + }, + "HTTP URL" : { + "displayName" : "HTTP URL", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP URL", + "sensitive" : false + }, + "Request OAuth2 Access Token Provider" : { + "displayName" : "Request OAuth2 Access Token Provider", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "Request OAuth2 Access Token Provider", + "sensitive" : false + }, + "Socket Idle Timeout" : { + "displayName" : "Socket Idle Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Idle Timeout", + "sensitive" : false + }, + "Response Redirects Enabled" : { + "displayName" : "Response Redirects Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Redirects Enabled", + "sensitive" : false + }, + "Socket Write Timeout" : { + "displayName" : "Socket Write Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Write Timeout", + "sensitive" : false + }, + "Request Header Attributes Pattern" : { + "displayName" : "Request Header Attributes Pattern", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Header Attributes Pattern", + "sensitive" : false + }, + "Response FlowFile Naming Strategy" : { + "displayName" : "Response FlowFile Naming Strategy", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response FlowFile Naming Strategy", + "sensitive" : false + }, + "Response Cache Enabled" : { + "displayName" : "Response Cache Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cache Enabled", + "sensitive" : false + }, + "Request Date Header Enabled" : { + "displayName" : "Request Date Header Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Date Header Enabled", + "sensitive" : false + }, + "Request Failure Penalization Enabled" : { + "displayName" : "Request Failure Penalization Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Failure Penalization Enabled", + "sensitive" : false + }, + "Response Body Attribute Size" : { + "displayName" : "Response Body Attribute Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Attribute Size", + "sensitive" : false + }, + "SSL Context Service" : { + "displayName" : "SSL Context Service", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "SSL Context Service", + "sensitive" : false + }, + "Response Generation Required" : { + "displayName" : "Response Generation Required", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Generation Required", + "sensitive" : false + }, + "Request User-Agent" : { + "displayName" : "Request User-Agent", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request User-Agent", + "sensitive" : false + }, + "Response Header Request Attributes Enabled" : { + "displayName" : "Response Header Request Attributes Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Header Request Attributes Enabled", + "sensitive" : false + }, + "HTTP Method" : { + "displayName" : "HTTP Method", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP Method", + "sensitive" : false + }, + "Request Username" : { + "displayName" : "Request Username", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Username", + "sensitive" : false + }, + "Request Content-Type" : { + "displayName" : "Request Content-Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Content-Type", + "sensitive" : false + }, + "Response Body Attribute Name" : { + "displayName" : "Response Body Attribute Name", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Attribute Name", + "sensitive" : false + }, + "Request Digest Authentication Enabled" : { + "displayName" : "Request Digest Authentication Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Digest Authentication Enabled", + "sensitive" : false + }, + "Request Multipart Form-Data Name" : { + "displayName" : "Request Multipart Form-Data Name", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Multipart Form-Data Name", + "sensitive" : false + }, + "Response Cache Size" : { + "displayName" : "Response Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cache Size", + "sensitive" : false + }, + "Response Body Ignored" : { + "displayName" : "Response Body Ignored", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Ignored", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "DISABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.InvokeHTTP", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "maxBackoffPeriod" : "10 mins", + "name" : "My Generate FlowFile Processor", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -286.0, + "y" : -250.0 + }, + "properties" : { + "character-set" : "UTF-8", + "File Size" : "0B", + "Batch Size" : "1", + "custom" : "custom", + "Unique FlowFiles" : "false", + "Data Format" : "Text" + }, + "propertyDescriptors" : { + "character-set" : { + "displayName" : "Character Set", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "character-set", + "sensitive" : false + }, + "File Size" : { + "displayName" : "File Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "File Size", + "sensitive" : false + }, + "mime-type" : { + "displayName" : "Mime Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "mime-type", + "sensitive" : false + }, + "generate-ff-custom-text" : { + "displayName" : "Custom Text", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "generate-ff-custom-text", + "sensitive" : false + }, + "Batch Size" : { + "displayName" : "Batch Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Batch Size", + "sensitive" : false + }, + "custom" : { + "displayName" : "custom", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "custom", + "sensitive" : false + }, + "Unique FlowFiles" : { + "displayName" : "Unique FlowFiles", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Unique FlowFiles", + "sensitive" : false + }, + "Data Format" : { + "displayName" : "Data Format", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Data Format", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "* * * * * ?", + "schedulingStrategy" : "CRON_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "ERROR", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 2, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "10 sec", + "position" : { + "x" : -16.0, + "y" : 24.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "10 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { + "Another one to delete" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "Another one to delete", + "parameters" : [ { + "description" : "", + "name" : "newSensitiveParam", + "provided" : false, + "sensitive" : true + }, { + "description" : "", + "name" : "secured", + "provided" : false, + "sensitive" : true + } ] + }, + "Test Parameter Context" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ "Another one to delete" ], + "name" : "Test Parameter Context", + "parameters" : [ { + "description" : "", + "name" : "addedParam", + "provided" : false, + "sensitive" : false, + "value" : "addedValue" + } ] + } + }, + "parameterProviders" : { }, + "snapshotMetadata" : { + "author" : "pvillard@datavolo.io", + "flowIdentifier" : "test", + "timestamp" : 0 + } +} \ No newline at end of file diff --git a/flow-diff/src/test/resources/flow_v6_parameter_value.json b/flow-diff/src/test/resources/flow_v6_parameter_value.json new file mode 100644 index 0000000..83dced3 --- /dev/null +++ b/flow-diff/src/test/resources/flow_v6_parameter_value.json @@ -0,0 +1,642 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "test", + "identifier" : "test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ { + "x" : 453.0, + "y" : 63.0 + }, { + "x" : 453.0, + "y" : 113.0 + } ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "a760d0b0-51e7-34af-922a-47366dfb2892", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 MB", + "backPressureObjectThreshold" : 1000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "name" : "UpdateAttribute", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "100 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "54de2ccb-cdf4-3072-a383-d9c8cc35404b", + "labelIndex" : 0, + "loadBalanceCompression" : "COMPRESS_ATTRIBUTES_AND_CONTENT", + "loadBalanceStrategy" : "ROUND_ROBIN", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "My Generate FlowFile Processor", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c", + "name" : "InvokeHTTP", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "adea8379-20c2-3a83-b1ee-a936a98bf829", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "name" : "My Generate FlowFile Processor", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "TestingFlowDiff", + "outputPorts" : [ ], + "parameterContextName" : "Test Parameter Context", + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "1a59f65f-8b3a-3db9-982e-e0d334bd7e9c", + "maxBackoffPeriod" : "10 mins", + "name" : "InvokeHTTP", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -496.0, + "y" : 24.0 + }, + "properties" : { + "Request Content-Encoding" : "DISABLED", + "nonSens" : "#{addedParam}", + "Request Multipart Form-Data Filename Enabled" : "true", + "Request Chunked Transfer-Encoding Enabled" : "false", + "HTTP/2 Disabled" : "False", + "Connection Timeout" : "5 secs", + "Response Cookie Strategy" : "DISABLED", + "Socket Read Timeout" : "15 secs", + "Socket Idle Connections" : "5", + "Request Body Enabled" : "true", + "HTTP URL" : "${url}", + "Socket Idle Timeout" : "5 mins", + "Response Redirects Enabled" : "True", + "Socket Write Timeout" : "15 secs", + "Response FlowFile Naming Strategy" : "RANDOM", + "Response Cache Enabled" : "false", + "Request Date Header Enabled" : "True", + "Request Failure Penalization Enabled" : "false", + "Response Body Attribute Size" : "256", + "Response Generation Required" : "false", + "Response Header Request Attributes Enabled" : "false", + "HTTP Method" : "GET", + "Request Content-Type" : "${mime.type}", + "Request Digest Authentication Enabled" : "false", + "Response Cache Size" : "10MB", + "Response Body Ignored" : "false" + }, + "propertyDescriptors" : { + "Request Content-Encoding" : { + "displayName" : "Request Content-Encoding", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Content-Encoding", + "sensitive" : false + }, + "proxy-configuration-service" : { + "displayName" : "Proxy Configuration Service", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "proxy-configuration-service", + "sensitive" : false + }, + "nonSens" : { + "displayName" : "nonSens", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "nonSens", + "sensitive" : false + }, + "Request Multipart Form-Data Filename Enabled" : { + "displayName" : "Request Multipart Form-Data Filename Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Multipart Form-Data Filename Enabled", + "sensitive" : false + }, + "Request Chunked Transfer-Encoding Enabled" : { + "displayName" : "Request Chunked Transfer-Encoding Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Chunked Transfer-Encoding Enabled", + "sensitive" : false + }, + "HTTP/2 Disabled" : { + "displayName" : "HTTP/2 Disabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP/2 Disabled", + "sensitive" : false + }, + "Connection Timeout" : { + "displayName" : "Connection Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Connection Timeout", + "sensitive" : false + }, + "Response Cookie Strategy" : { + "displayName" : "Response Cookie Strategy", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cookie Strategy", + "sensitive" : false + }, + "Request Password" : { + "displayName" : "Request Password", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Password", + "sensitive" : true + }, + "Socket Read Timeout" : { + "displayName" : "Socket Read Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Read Timeout", + "sensitive" : false + }, + "Socket Idle Connections" : { + "displayName" : "Socket Idle Connections", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Idle Connections", + "sensitive" : false + }, + "Request Body Enabled" : { + "displayName" : "Request Body Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Body Enabled", + "sensitive" : false + }, + "sensitiveDynamicProp" : { + "displayName" : "sensitiveDynamicProp", + "dynamic" : true, + "identifiesControllerService" : false, + "name" : "sensitiveDynamicProp", + "sensitive" : true + }, + "HTTP URL" : { + "displayName" : "HTTP URL", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP URL", + "sensitive" : false + }, + "Request OAuth2 Access Token Provider" : { + "displayName" : "Request OAuth2 Access Token Provider", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "Request OAuth2 Access Token Provider", + "sensitive" : false + }, + "Socket Idle Timeout" : { + "displayName" : "Socket Idle Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Idle Timeout", + "sensitive" : false + }, + "Response Redirects Enabled" : { + "displayName" : "Response Redirects Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Redirects Enabled", + "sensitive" : false + }, + "Socket Write Timeout" : { + "displayName" : "Socket Write Timeout", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Socket Write Timeout", + "sensitive" : false + }, + "Request Header Attributes Pattern" : { + "displayName" : "Request Header Attributes Pattern", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Header Attributes Pattern", + "sensitive" : false + }, + "Response FlowFile Naming Strategy" : { + "displayName" : "Response FlowFile Naming Strategy", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response FlowFile Naming Strategy", + "sensitive" : false + }, + "Response Cache Enabled" : { + "displayName" : "Response Cache Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cache Enabled", + "sensitive" : false + }, + "Request Date Header Enabled" : { + "displayName" : "Request Date Header Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Date Header Enabled", + "sensitive" : false + }, + "Request Failure Penalization Enabled" : { + "displayName" : "Request Failure Penalization Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Failure Penalization Enabled", + "sensitive" : false + }, + "Response Body Attribute Size" : { + "displayName" : "Response Body Attribute Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Attribute Size", + "sensitive" : false + }, + "SSL Context Service" : { + "displayName" : "SSL Context Service", + "dynamic" : false, + "identifiesControllerService" : true, + "name" : "SSL Context Service", + "sensitive" : false + }, + "Response Generation Required" : { + "displayName" : "Response Generation Required", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Generation Required", + "sensitive" : false + }, + "Request User-Agent" : { + "displayName" : "Request User-Agent", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request User-Agent", + "sensitive" : false + }, + "Response Header Request Attributes Enabled" : { + "displayName" : "Response Header Request Attributes Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Header Request Attributes Enabled", + "sensitive" : false + }, + "HTTP Method" : { + "displayName" : "HTTP Method", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "HTTP Method", + "sensitive" : false + }, + "Request Username" : { + "displayName" : "Request Username", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Username", + "sensitive" : false + }, + "Request Content-Type" : { + "displayName" : "Request Content-Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Content-Type", + "sensitive" : false + }, + "Response Body Attribute Name" : { + "displayName" : "Response Body Attribute Name", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Attribute Name", + "sensitive" : false + }, + "Request Digest Authentication Enabled" : { + "displayName" : "Request Digest Authentication Enabled", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Digest Authentication Enabled", + "sensitive" : false + }, + "Request Multipart Form-Data Name" : { + "displayName" : "Request Multipart Form-Data Name", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Request Multipart Form-Data Name", + "sensitive" : false + }, + "Response Cache Size" : { + "displayName" : "Response Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Cache Size", + "sensitive" : false + }, + "Response Body Ignored" : { + "displayName" : "Response Body Ignored", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Response Body Ignored", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "DISABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.InvokeHTTP", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "50a3b081-d54d-3ad8-b74c-caa7fef59bb2", + "maxBackoffPeriod" : "10 mins", + "name" : "My Generate FlowFile Processor", + "penaltyDuration" : "30 sec", + "position" : { + "x" : -286.0, + "y" : -250.0 + }, + "properties" : { + "character-set" : "UTF-8", + "File Size" : "0B", + "Batch Size" : "1", + "Unique FlowFiles" : "false", + "Data Format" : "Text" + }, + "propertyDescriptors" : { + "character-set" : { + "displayName" : "Character Set", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "character-set", + "sensitive" : false + }, + "File Size" : { + "displayName" : "File Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "File Size", + "sensitive" : false + }, + "mime-type" : { + "displayName" : "Mime Type", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "mime-type", + "sensitive" : false + }, + "generate-ff-custom-text" : { + "displayName" : "Custom Text", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "generate-ff-custom-text", + "sensitive" : false + }, + "Batch Size" : { + "displayName" : "Batch Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Batch Size", + "sensitive" : false + }, + "Unique FlowFiles" : { + "displayName" : "Unique FlowFiles", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Unique FlowFiles", + "sensitive" : false + }, + "Data Format" : { + "displayName" : "Data Format", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Data Format", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "* * * * * ?", + "schedulingStrategy" : "CRON_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "ERROR", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2024.09.11-1" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 2, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "2d8da922-fd1f-3519-9d54-6482dfd42c56", + "maxBackoffPeriod" : "10 mins", + "name" : "UpdateAttribute", + "penaltyDuration" : "10 sec", + "position" : { + "x" : -16.0, + "y" : 24.0 + }, + "properties" : { + "Store State" : "Do not store state", + "canonical-value-lookup-cache-size" : "100" + }, + "propertyDescriptors" : { + "Delete Attributes Expression" : { + "displayName" : "Delete Attributes Expression", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Delete Attributes Expression", + "sensitive" : false + }, + "Store State" : { + "displayName" : "Store State", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Store State", + "sensitive" : false + }, + "canonical-value-lookup-cache-size" : { + "displayName" : "Cache Value Lookup Cache Size", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "canonical-value-lookup-cache-size", + "sensitive" : false + }, + "Stateful Variables Initial Value" : { + "displayName" : "Stateful Variables Initial Value", + "dynamic" : false, + "identifiesControllerService" : false, + "name" : "Stateful Variables Initial Value", + "sensitive" : false + } + }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 25, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "10 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { + "Another one to delete" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "Another one to delete", + "parameters" : [ { + "description" : "", + "name" : "newSensitiveParam", + "provided" : false, + "sensitive" : true + }, { + "description" : "", + "name" : "secured", + "provided" : false, + "sensitive" : true + } ] + }, + "Test Parameter Context" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ "Another one to delete" ], + "name" : "Test Parameter Context", + "parameters" : [ { + "description" : "", + "name" : "addedParam", + "provided" : false, + "sensitive" : false, + "value" : "newValue" + } ] + } + }, + "parameterProviders" : { }, + "snapshotMetadata" : { + "author" : "pvillard@datavolo.io", + "flowIdentifier" : "test", + "timestamp" : 0 + } +} \ No newline at end of file