From a29feb9c5c85305b31fa4643baf83bb11874c033 Mon Sep 17 00:00:00 2001 From: Gabor Gyimesi Date: Tue, 29 Oct 2024 13:09:08 +0100 Subject: [PATCH] MINIFICPP-2376 Add support for parameter context inheritance --- CONFIGURE.md | 26 +- .../tests/unit/FlowJsonTests.cpp | 331 +++++++++++++++++- .../tests/unit/YamlConfigurationTests.cpp | 264 ++++++++++++++ libminifi/include/core/ParameterContext.h | 10 + libminifi/include/core/flow/FlowSchema.h | 1 + .../core/flow/StructuredConfiguration.h | 1 + libminifi/src/core/ParameterContext.cpp | 5 + libminifi/src/core/flow/FlowSchema.cpp | 6 +- .../src/core/flow/StructuredConfiguration.cpp | 56 +++ 9 files changed, 696 insertions(+), 4 deletions(-) diff --git a/CONFIGURE.md b/CONFIGURE.md index 51e6219ae2..e29af41d0f 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -139,12 +139,25 @@ Processor properties in flow configurations can be parameterized using parameter - `#` character can be used to escape the parameter syntax. E.g. if the `parameterName` parameter's value is `xxx` then `#{parameterName}` will be replaced with `xxx`, `##{parameterName}` will be replaced with `#{parameterName}`, and `#####{parameterName}` will be replaced with `##xxx`. - Sensitive parameters can only be assigned to sensitive properties and non-sensitive parameters can only be assigned to non-sensitive properties. +Parameter contexts can be inherited from multiple other parameter contexts. The order of the parameter inheritance matters in the way that if a parameter is present in multiple parameter contexts, the parameter value assigned to the first parameter in the inheritance order will be used. No circular inheritance is allowed. + An example for using parameters in a JSON configuration file: ```json { "parameterContexts": [ { + "identifier": "235e6b47-ea22-45cd-a472-545801db98e6", + "name": "common-parameter-context", + "description": "Common parameter context", + "parameters": [ + { + "name": "common_timeout", + "description": "Common timeout seconds", + "sensitive": false, + "value": "30" + } + ], "identifier": "804e6b47-ea22-45cd-a472-545801db98e6", "name": "root-process-group-context", "description": "Root process group parameter context", @@ -155,7 +168,8 @@ An example for using parameters in a JSON configuration file: "sensitive": false, "value": "/tmp/tail/file/path" } - ] + ], + "inheritedParameterContexts": ["common-parameter-context"] } ], "rootGroup": { @@ -194,6 +208,14 @@ An example for using parameters in a YAML configuration file: Flow Controller: name: MiNiFi Flow Parameter Contexts: + - id: 235e6b47-ea22-45cd-a472-545801db98e6 + name: common-parameter-context + description: Common parameter context + Parameters: + - name: common_timeout + description: 'Common timeout seconds' + sensitive: false + value: 30 - id: 804e6b47-ea22-45cd-a472-545801db98e6 name: root-process-group-context description: Root process group parameter context @@ -202,6 +224,8 @@ An example for using parameters in a YAML configuration file: description: 'Base dir of tailed files' sensitive: false value: /tmp/tail/file/path + Inherited Parameter Contexts: + - common-parameter-context Processors: - name: Tail test_file1.log id: 83b58f9f-e661-4634-96fb-0e82b92becdf diff --git a/extensions/standard-processors/tests/unit/FlowJsonTests.cpp b/extensions/standard-processors/tests/unit/FlowJsonTests.cpp index 4eb54198a5..7cad051854 100644 --- a/extensions/standard-processors/tests/unit/FlowJsonTests.cpp +++ b/extensions/standard-processors/tests/unit/FlowJsonTests.cpp @@ -29,6 +29,7 @@ #include "Funnel.h" #include "core/Resource.h" #include "utils/crypto/property_encryption/PropertyEncryptionUtils.h" +#include "unit/TestUtils.h" using namespace std::literals::chrono_literals; @@ -1029,7 +1030,6 @@ TEST_CASE("NiFi flow json can use alternative targetUris field") { REQUIRE(port->getProperty("Port UUID") == "00000000-0000-0000-0000-000000000005"); } - TEST_CASE("Test parameters in controller services") { ConfigurationTestController test_controller; auto context = test_controller.getContext(); @@ -1149,4 +1149,333 @@ TEST_CASE("Parameters can be used in controller services in nested process group CHECK(impl->getProperty("Private Key").value() == "/opt/secrets/private-key.pem"); } +TEST_CASE("Test parameter context inheritance") { + ConfigurationTestController test_controller; + auto context = test_controller.getContext(); + auto encrypted_parameter_value = minifi::utils::crypto::property_encryption::encrypt("value1", *context.sensitive_values_encryptor); + auto encrypted_sensitive_property_value = minifi::utils::crypto::property_encryption::encrypt("#{my_new_parameter}", *context.sensitive_values_encryptor); + core::flow::AdaptiveConfiguration config(context); + + static const std::string CONFIG_JSON = + fmt::format(R"( +{{ + "parameterContexts": [ + {{ + "identifier": "721e10b7-8e00-3188-9a27-476cca376978", + "name": "inherited-context", + "description": "inherited parameter context", + "parameters": [ + {{ + "name": "my_new_parameter", + "description": "", + "sensitive": true, + "value": "{}" + }} + ], + "inheritedParameterContexts": ["base-context"] + }}, + {{ + "identifier": "521e10b7-8e00-3188-9a27-476cca376351", + "name": "base-context", + "description": "my base parameter context", + "parameters": [ + {{ + "name": "my_old_parameter", + "description": "", + "sensitive": false, + "value": "old_value" + }} + ] + }} + ], + "rootGroup": {{ + "name": "MiNiFi Flow", + "processors": [{{ + "identifier": "00000000-0000-0000-0000-000000000001", + "name": "MyProcessor", + "type": "org.apache.nifi.processors.DummyFlowJsonProcessor", + "schedulingStrategy": "TIMER_DRIVEN", + "schedulingPeriod": "3 sec", + "properties": {{ + "Sensitive Property": "{}", + "Simple Property": "#{{my_old_parameter}}" + }} + }}], + "parameterContextName": "inherited-context" + }} +}})", encrypted_parameter_value, encrypted_sensitive_property_value); + + std::unique_ptr flow = config.getRootFromPayload(CONFIG_JSON); + REQUIRE(flow); + + auto* proc = flow->findProcessorByName("MyProcessor"); + REQUIRE(proc); + REQUIRE(proc->getProperty("Sensitive Property") == "value1"); + REQUIRE(proc->getProperty("Simple Property") == "old_value"); +} + +TEST_CASE("Parameter context can not inherit from a itself") { + ConfigurationTestController test_controller; + + core::flow::AdaptiveConfiguration config(test_controller.getContext()); + + static const std::string CONFIG_JSON = + R"( +{ + "parameterContexts": [ + { + "identifier": "521e10b7-8e00-3188-9a27-476cca376351", + "name": "base-context", + "description": "my base parameter context", + "parameters": [ + { + "name": "my_old_parameter", + "description": "", + "sensitive": false, + "value": "old_value" + } + ], + "inheritedParameterContexts": ["base-context"] + } + ], + "rootGroup": { + "name": "MiNiFi Flow", + "processors": [{ + "identifier": "00000000-0000-0000-0000-000000000001", + "name": "MyProcessor", + "type": "org.apache.nifi.processors.DummyFlowJsonProcessor", + "schedulingStrategy": "TIMER_DRIVEN", + "schedulingPeriod": "3 sec", + "properties": { + "Simple Property": "#{my_old_parameter}" + } + }], + "parameterContextName": "base-context" + } +})"; + + REQUIRE_THROWS_WITH(config.getRootFromPayload(CONFIG_JSON), "Inherited parameter context 'base-context' cannot be the same as the parameter context!"); +} + +TEST_CASE("Parameter context can not inherit from non-existing parameter context") { + ConfigurationTestController test_controller; + + core::flow::AdaptiveConfiguration config(test_controller.getContext()); + + static const std::string CONFIG_JSON = + R"( +{ + "parameterContexts": [ + { + "identifier": "521e10b7-8e00-3188-9a27-476cca376351", + "name": "base-context", + "description": "my base parameter context", + "parameters": [ + { + "name": "my_old_parameter", + "description": "", + "sensitive": false, + "value": "old_value" + } + ], + "inheritedParameterContexts": ["unknown"] + } + ], + "rootGroup": { + "name": "MiNiFi Flow", + "processors": [{ + "identifier": "00000000-0000-0000-0000-000000000001", + "name": "MyProcessor", + "type": "org.apache.nifi.processors.DummyFlowJsonProcessor", + "schedulingStrategy": "TIMER_DRIVEN", + "schedulingPeriod": "3 sec", + "properties": { + "Simple Property": "#{my_old_parameter}" + } + }], + "parameterContextName": "base-context" + } +})"; + + REQUIRE_THROWS_WITH(config.getRootFromPayload(CONFIG_JSON), "Inherited parameter context 'unknown' does not exist!"); +} + +TEST_CASE("Cycles are not allowed in parameter context inheritance") { + ConfigurationTestController test_controller; + + core::flow::AdaptiveConfiguration config(test_controller.getContext()); + + static const std::string CONFIG_JSON = + R"( +{ + "parameterContexts": [ + { + "identifier": "123e10b7-8e00-3188-9a27-476cca376351", + "name": "a-context", + "description": "", + "parameters": [ + { + "name": "a_parameter", + "description": "", + "sensitive": false, + "value": "a_value" + } + ], + "inheritedParameterContexts": ["c-context"] + }, + { + "identifier": "456e10b7-8e00-3188-9a27-476cca376351", + "name": "b-context", + "description": "", + "parameters": [ + { + "name": "b_parameter", + "description": "", + "sensitive": false, + "value": "b_value" + } + ], + "inheritedParameterContexts": ["a-context"] + }, + { + "identifier": "789e10b7-8e00-3188-9a27-476cca376351", + "name": "c-context", + "description": "", + "parameters": [ + { + "name": "c_parameter", + "description": "", + "sensitive": false, + "value": "c_value" + } + ], + "inheritedParameterContexts": ["d-context", "b-context"] + }, + { + "identifier": "101e10b7-8e00-3188-9a27-476cca376351", + "name": "d-context", + "description": "", + "parameters": [ + { + "name": "d_parameter", + "description": "", + "sensitive": false, + "value": "d_value" + } + ], + "inheritedParameterContexts": [] + } + ], + "rootGroup": { + "name": "MiNiFi Flow", + "processors": [{ + "identifier": "00000000-0000-0000-0000-000000000001", + "name": "MyProcessor", + "type": "org.apache.nifi.processors.DummyFlowJsonProcessor", + "schedulingStrategy": "TIMER_DRIVEN", + "schedulingPeriod": "3 sec", + "properties": { + "Simple Property": "#{my_old_parameter}" + } + }], + "parameterContextName": "c-context" + } +})"; + + REQUIRE_THROWS_AS(config.getRootFromPayload(CONFIG_JSON), std::invalid_argument); + REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Circular references in Parameter Context inheritance are not allowed. Inheritance cycle was detected in parameter context")); +} + +TEST_CASE("Parameter context inheritance order is respected") { + ConfigurationTestController test_controller; + core::flow::AdaptiveConfiguration config(test_controller.getContext()); + + static const std::string CONFIG_JSON = + R"( +{ + "parameterContexts": [ + { + "identifier": "721e10b7-8e00-3188-9a27-476cca376978", + "name": "a-context", + "description": "", + "parameters": [ + { + "name": "a_parameter", + "description": "", + "sensitive": false, + "value": "1" + }, + { + "name": "b_parameter", + "description": "", + "sensitive": false, + "value": "2" + } + ] + }, + { + "identifier": "521e10b7-8e00-3188-9a27-476cca376351", + "name": "b-context", + "description": "", + "parameters": [ + { + "name": "b_parameter", + "description": "", + "sensitive": false, + "value": "3" + }, + { + "name": "c_parameter", + "description": "", + "sensitive": false, + "value": "4" + } + ] + }, + { + "identifier": "123e10b7-8e00-3188-9a27-476cca376351", + "name": "c-context", + "description": "", + "parameters": [ + { + "name": "c_parameter", + "description": "", + "sensitive": false, + "value": "5" + } + ], + "inheritedParameterContexts": ["b-context", "a-context"] + } + ], + "rootGroup": { + "name": "MiNiFi Flow", + "processors": [{ + "identifier": "00000000-0000-0000-0000-000000000001", + "name": "MyProcessor", + "type": "org.apache.nifi.processors.DummyFlowJsonProcessor", + "schedulingStrategy": "TIMER_DRIVEN", + "schedulingPeriod": "3 sec", + "properties": { + "My A Property": "#{a_parameter}", + "My B Property": "#{b_parameter}", + "My C Property": "#{c_parameter}" + } + }], + "parameterContextName": "c-context" + } +})"; + + std::unique_ptr flow = config.getRootFromPayload(CONFIG_JSON); + REQUIRE(flow); + + auto* proc = flow->findProcessorByName("MyProcessor"); + std::string value; + REQUIRE(proc->getDynamicProperty("My A Property", value)); + CHECK(value == "1"); + REQUIRE(proc->getDynamicProperty("My B Property", value)); + CHECK(value == "3"); + REQUIRE(proc->getDynamicProperty("My C Property", value)); + CHECK(value == "5"); +} + } // namespace org::apache::nifi::minifi::test diff --git a/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp b/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp index e2538662ba..d6c4053c40 100644 --- a/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp +++ b/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp @@ -1876,4 +1876,268 @@ Process Groups: CHECK(impl->getProperty("Private Key").value() == "/opt/secrets/private-key.pem"); } +TEST_CASE("Test parameter context inheritance", "[YamlConfiguration]") { + ConfigurationTestController test_controller; + auto context = test_controller.getContext(); + auto encrypted_parameter_value = minifi::utils::crypto::property_encryption::encrypt("value1", *context.sensitive_values_encryptor); + auto encrypted_sensitive_property_value = minifi::utils::crypto::property_encryption::encrypt("#{my_new_parameter}", *context.sensitive_values_encryptor); + core::YamlConfiguration yaml_config(context); + + static const std::string TEST_CONFIG_YAML = + fmt::format(R"( +MiNiFi Config Version: 3 +Flow Controller: + name: flowconfig +Parameter Contexts: + - id: 721e10b7-8e00-3188-9a27-476cca376978 + name: inherited-context + description: my parameter context + Parameters: + - name: my_new_parameter + description: inherited parameter context + sensitive: true + value: {} + Inherited Parameter Contexts: [base-context] + - id: 521e10b7-8e00-3188-9a27-476cca376351 + name: base-context + description: my base parameter context + Parameters: + - name: my_old_parameter + description: '' + sensitive: false + value: old_value +Processors: +- id: b0c04f28-0158-1000-0000-000000000000 + name: DummyFlowYamlProcessor + class: org.apache.nifi.processors.DummyFlowYamlProcessor + max concurrent tasks: 1 + scheduling strategy: TIMER_DRIVEN + scheduling period: 1 sec + auto-terminated relationships list: [success] + Properties: + Simple Property: "#{{my_old_parameter}}" + Sensitive Property: {} +Parameter Context Name: inherited-context + )", encrypted_parameter_value, encrypted_sensitive_property_value); + + std::unique_ptr flow = yaml_config.getRootFromPayload(TEST_CONFIG_YAML); + REQUIRE(flow); + auto* proc = flow->findProcessorByName("DummyFlowYamlProcessor"); + REQUIRE(proc); + REQUIRE(proc->getProperty("Simple Property") == "old_value"); + REQUIRE(proc->getProperty("Sensitive Property") == "value1"); +} + +TEST_CASE("Parameter context can not inherit from a itself", "[YamlConfiguration]") { + ConfigurationTestController test_controller; + core::YamlConfiguration yaml_config(test_controller.getContext()); + + static const std::string TEST_CONFIG_YAML = + R"( +MiNiFi Config Version: 3 +Flow Controller: + name: flowconfig +Parameter Contexts: + - id: 521e10b7-8e00-3188-9a27-476cca376351 + name: base-context + description: my base parameter context + Parameters: + - name: my_old_parameter + description: '' + sensitive: false + value: old_value + Inherited Parameter Contexts: + - base-context +Processors: +- id: b0c04f28-0158-1000-0000-000000000000 + name: DummyFlowYamlProcessor + class: org.apache.nifi.processors.DummyFlowYamlProcessor + max concurrent tasks: 1 + scheduling strategy: TIMER_DRIVEN + scheduling period: 1 sec + auto-terminated relationships list: [success] + Properties: + Simple Property: "#{my_old_parameter}" +Parameter Context Name: inherited-context + )"; + + REQUIRE_THROWS_WITH(yaml_config.getRootFromPayload(TEST_CONFIG_YAML), "Inherited parameter context 'base-context' cannot be the same as the parameter context!"); +} + +TEST_CASE("Parameter context can not inherit from non-existing parameter context", "[YamlConfiguration]") { + ConfigurationTestController test_controller; + core::YamlConfiguration yaml_config(test_controller.getContext()); + + static const std::string TEST_CONFIG_YAML = + R"( +MiNiFi Config Version: 3 +Flow Controller: + name: flowconfig +Parameter Contexts: + - id: 521e10b7-8e00-3188-9a27-476cca376351 + name: base-context + description: my base parameter context + Parameters: + - name: my_old_parameter + description: '' + sensitive: false + value: old_value + Inherited Parameter Contexts: [unknown] +Processors: +- id: b0c04f28-0158-1000-0000-000000000000 + name: DummyFlowYamlProcessor + class: org.apache.nifi.processors.DummyFlowYamlProcessor + max concurrent tasks: 1 + scheduling strategy: TIMER_DRIVEN + scheduling period: 1 sec + auto-terminated relationships list: [success] + Properties: + Simple Property: "#{my_old_parameter}" +Parameter Context Name: inherited-context + )"; + + REQUIRE_THROWS_WITH(yaml_config.getRootFromPayload(TEST_CONFIG_YAML), "Inherited parameter context 'unknown' does not exist!"); +} + +TEST_CASE("Cycles are not allowed in parameter context inheritance", "[YamlConfiguration]") { + ConfigurationTestController test_controller; + core::YamlConfiguration yaml_config(test_controller.getContext()); + + static const std::string TEST_CONFIG_YAML = + R"( +MiNiFi Config Version: 3 +Flow Controller: + name: flowconfig +Parameter Contexts: + - id: 123e10b7-8e00-3188-9a27-476cca376351 + name: a-context + description: '' + Parameters: + - name: a_parameter + description: '' + sensitive: false + value: a_value + Inherited Parameter Contexts: + - c-context + - id: 456e10b7-8e00-3188-9a27-476cca376351 + name: b-context + description: '' + Parameters: + - name: b_parameter + description: '' + sensitive: false + value: b_value + Inherited Parameter Contexts: + - a-context + - id: 789e10b7-8e00-3188-9a27-476cca376351 + name: c-context + description: '' + Parameters: + - name: c_parameter + description: '' + sensitive: false + value: c_value + Inherited Parameter Contexts: + - d-context + - b-context + - id: 101e10b7-8e00-3188-9a27-476cca376351 + name: d-context + description: '' + Parameters: + - name: d_parameter + description: '' + sensitive: false + value: d_value + Inherited Parameter Contexts: [] +Processors: +- id: b0c04f28-0158-1000-0000-000000000000 + name: DummyFlowYamlProcessor + class: org.apache.nifi.processors.DummyFlowYamlProcessor + max concurrent tasks: 1 + scheduling strategy: TIMER_DRIVEN + scheduling period: 1 sec + auto-terminated relationships list: [success] + Properties: + Simple Property: "#{{my_old_parameter}}" + Sensitive Property: {} +Parameter Context Name: inherited-context + )"; + + REQUIRE_THROWS_AS(yaml_config.getRootFromPayload(TEST_CONFIG_YAML), std::invalid_argument); + REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Circular references in Parameter Context inheritance are not allowed. Inheritance cycle was detected in parameter context")); +} + +TEST_CASE("Parameter context inheritance order is respected", "[YamlConfiguration]") { + ConfigurationTestController test_controller; + core::YamlConfiguration yaml_config(test_controller.getContext()); + + static const std::string TEST_CONFIG_YAML = + R"( +MiNiFi Config Version: 3 +Flow Controller: + name: flowconfig +Parameter Contexts: + - id: 721e10b7-8e00-3188-9a27-476cca376978 + name: a-context + description: '' + Parameters: + - name: a_parameter + description: '' + sensitive: false + value: 1 + - name: b_parameter + description: '' + sensitive: false + value: 2 + - id: 521e10b7-8e00-3188-9a27-476cca376351 + name: b-context + description: '' + Parameters: + - name: b_parameter + description: '' + sensitive: false + value: 3 + - name: c_parameter + description: '' + sensitive: false + value: 4 + - id: 123e10b7-8e00-3188-9a27-476cca376351 + name: c-context + description: '' + Parameters: + - name: c_parameter + description: '' + sensitive: false + value: 5 + Inherited Parameter Contexts: + - b-context + - a-context +Processors: +- id: b0c04f28-0158-1000-0000-000000000000 + name: DummyFlowYamlProcessor + class: org.apache.nifi.processors.DummyFlowYamlProcessor + max concurrent tasks: 1 + scheduling strategy: TIMER_DRIVEN + scheduling period: 1 sec + auto-terminated relationships list: [success] + Properties: + My A Property: "#{a_parameter}" + My B Property: "#{b_parameter}" + My C Property: "#{c_parameter}" +Parameter Context Name: c-context + )"; + + std::unique_ptr flow = yaml_config.getRootFromPayload(TEST_CONFIG_YAML); + REQUIRE(flow); + auto* proc = flow->findProcessorByName("DummyFlowYamlProcessor"); + REQUIRE(proc); + std::string value; + REQUIRE(proc->getDynamicProperty("My A Property", value)); + CHECK(value == "1"); + REQUIRE(proc->getDynamicProperty("My B Property", value)); + CHECK(value == "3"); + REQUIRE(proc->getDynamicProperty("My C Property", value)); + CHECK(value == "5"); +} + } // namespace org::apache::nifi::minifi::test diff --git a/libminifi/include/core/ParameterContext.h b/libminifi/include/core/ParameterContext.h index 1b795414cf..c7a11b5930 100644 --- a/libminifi/include/core/ParameterContext.h +++ b/libminifi/include/core/ParameterContext.h @@ -53,13 +53,23 @@ class ParameterContext : public CoreComponent { void addParameter(const Parameter ¶meter); std::optional getParameter(const std::string &name) const; + const std::unordered_map& getParameters() const { return parameters_; } + void addInheritedParameterContext(gsl::not_null parameter_context) { + inherited_parameter_contexts_.push_back(parameter_context); + } + + const std::vector>& getInheritedParameterContexts() const { + return inherited_parameter_contexts_; + } + private: std::string description_; std::unordered_map parameters_; + std::vector> inherited_parameter_contexts_; }; } // namespace org::apache::nifi::minifi::core diff --git a/libminifi/include/core/flow/FlowSchema.h b/libminifi/include/core/flow/FlowSchema.h index 6cbad427a0..9860cb86e8 100644 --- a/libminifi/include/core/flow/FlowSchema.h +++ b/libminifi/include/core/flow/FlowSchema.h @@ -86,6 +86,7 @@ struct FlowSchema { Keys value; Keys parameter_context_name; Keys sensitive; + Keys inherited_parameter_contexts; static FlowSchema getDefault(); static FlowSchema getNiFiFlowJson(); diff --git a/libminifi/include/core/flow/StructuredConfiguration.h b/libminifi/include/core/flow/StructuredConfiguration.h index 23cbf28473..52b49f69e2 100644 --- a/libminifi/include/core/flow/StructuredConfiguration.h +++ b/libminifi/include/core/flow/StructuredConfiguration.h @@ -234,6 +234,7 @@ class StructuredConfiguration : public FlowConfiguration { * @param reason */ void raiseComponentError(const std::string &component_name, const std::string §ion, const std::string &reason) const; + void verifyNoInheritanceCycles() const; }; } // namespace org::apache::nifi::minifi::core::flow diff --git a/libminifi/src/core/ParameterContext.cpp b/libminifi/src/core/ParameterContext.cpp index b4501930f3..460507c92c 100644 --- a/libminifi/src/core/ParameterContext.cpp +++ b/libminifi/src/core/ParameterContext.cpp @@ -31,6 +31,11 @@ std::optional ParameterContext::getParameter(const std::string &name) if (it != parameters_.end()) { return it->second; } + for (const auto& parameter_context : inherited_parameter_contexts_) { + if (auto parameter = parameter_context->getParameter(name)) { + return parameter; + } + } return std::nullopt; } diff --git a/libminifi/src/core/flow/FlowSchema.cpp b/libminifi/src/core/flow/FlowSchema.cpp index 4ab3aba0b3..b58edc6982 100644 --- a/libminifi/src/core/flow/FlowSchema.cpp +++ b/libminifi/src/core/flow/FlowSchema.cpp @@ -82,7 +82,8 @@ FlowSchema FlowSchema::getDefault() { .description = {"description"}, .value = {"value"}, .parameter_context_name = {"Parameter Context Name"}, - .sensitive = {"sensitive"} + .sensitive = {"sensitive"}, + .inherited_parameter_contexts = {"Inherited Parameter Contexts"} }; } @@ -149,7 +150,8 @@ FlowSchema FlowSchema::getNiFiFlowJson() { .description = {"description"}, .value = {"value"}, .parameter_context_name = {"parameterContextName"}, - .sensitive = {"sensitive"} + .sensitive = {"sensitive"}, + .inherited_parameter_contexts = {"inheritedParameterContexts"} }; } diff --git a/libminifi/src/core/flow/StructuredConfiguration.cpp b/libminifi/src/core/flow/StructuredConfiguration.cpp index d79550b460..fd8ad4325e 100644 --- a/libminifi/src/core/flow/StructuredConfiguration.cpp +++ b/libminifi/src/core/flow/StructuredConfiguration.cpp @@ -139,6 +139,41 @@ std::unique_ptr StructuredConfiguration::getRootFrom(const N } } +namespace { +bool hasInheritanceCycle(const ParameterContext& parameter_context, std::unordered_set& visited_parameter_contexts, std::unordered_set& current_stack) { + if (current_stack.contains(parameter_context.getName())) { + return true; + } + + if (visited_parameter_contexts.contains(parameter_context.getName())) { + return false; + } + + current_stack.insert(parameter_context.getName()); + visited_parameter_contexts.insert(parameter_context.getName()); + + for (const auto& inherited_parameter_context : parameter_context.getInheritedParameterContexts()) { + if (hasInheritanceCycle(*inherited_parameter_context, visited_parameter_contexts, current_stack)) { + return true; + } + } + + current_stack.erase(parameter_context.getName()); + + return false; +} +} // namespace + +void StructuredConfiguration::verifyNoInheritanceCycles() const { + std::unordered_set visited_parameter_contexts; + std::unordered_set current_stack; + for (const auto& [parameter_context_name, parameter_context] : parameter_contexts_) { + if (hasInheritanceCycle(*parameter_context, visited_parameter_contexts, current_stack)) { + throw std::invalid_argument("Circular references in Parameter Context inheritance are not allowed. Inheritance cycle was detected in parameter context '" + parameter_context->getName() + "'"); + } + } +} + void StructuredConfiguration::parseParameterContexts(const Node& parameter_contexts_node) { if (!parameter_contexts_node || !parameter_contexts_node.isSequence()) { return; @@ -172,6 +207,27 @@ void StructuredConfiguration::parseParameterContexts(const Node& parameter_conte parameter_contexts_.emplace(name, gsl::make_not_null(std::move(parameter_context))); } + + for (const auto& parameter_context_node : parameter_contexts_node) { + if (!isFieldPresent(parameter_context_node, schema_.inherited_parameter_contexts[0])) { + continue; + } + auto inherited_parameters_node = parameter_context_node[schema_.inherited_parameter_contexts]; + for (const auto& inherited_parameter_context_name : inherited_parameters_node) { + auto name = inherited_parameter_context_name.getString().value(); + if (parameter_contexts_.find(name) == parameter_contexts_.end()) { + throw std::invalid_argument("Inherited parameter context '" + name + "' does not exist!"); + } + + auto parameter_context_name = parameter_context_node[schema_.name].getString().value(); + if (parameter_context_name == name) { + throw std::invalid_argument("Inherited parameter context '" + name + "' cannot be the same as the parameter context!"); + } + gsl::not_null context = gsl::make_not_null(parameter_contexts_.at(name).get()); + parameter_contexts_.at(parameter_context_name)->addInheritedParameterContext(context); + } + } + verifyNoInheritanceCycles(); } void StructuredConfiguration::parseProcessorNode(const Node& processors_node, core::ProcessGroup* parentGroup) {