This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 2feadfbe654 NIFI-15909 Resolve providerId from ConnectorConfigProvider
values (#11209)
2feadfbe654 is described below
commit 2feadfbe6543b5b2d622cf1e697b41dfc08f6dd5
Author: Kevin Doran <[email protected]>
AuthorDate: Wed May 6 15:47:09 2026 -0400
NIFI-15909 Resolve providerId from ConnectorConfigProvider values (#11209)
* NIFI-15909 Resolve providerId from ConnectorConfigProvider values
Resolve providerId on SECRET_REFERENCE entries returned by a
ConnectorConfigurationProvider
when only providerName is populated. The id is filled in only when the name
unambiguously
matches a single registered parameter provider; zero or ambiguous matches
leave providerId
null so the UI surfaces the SECRET_REFERENCE as invalid for
re-configuration.
---
.../connector/StandardConnectorRepository.java | 85 ++++++++++++++++++++++
.../connector/TestStandardConnectorRepository.java | 70 ++++++++++++++++++
2 files changed, 155 insertions(+)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index 0b0382ccfc2..ffd8f2e7a76 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -22,6 +22,7 @@ import org.apache.nifi.asset.Asset;
import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.connector.secrets.SecretsManager;
+import org.apache.nifi.controller.ParameterProviderNode;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
@@ -190,6 +191,12 @@ public class StandardConnectorRepository implements
ConnectorRepository {
// Determine effective name, working config, and ScheduledState
final ConnectorWorkingConfiguration providerConfig =
directive.getWorkingConfiguration();
+ // Enrich provider-sourced SECRET_REFERENCE values with providerId
before they are compared
+ // against the in-memory configuration or passed into
inheritConfiguration.
+ if (providerConfig != null) {
+
resolveSecretReferencesFromProvider(providerConfig.getWorkingFlowConfiguration());
+ }
+
final String effectiveName = (providerConfig != null &&
providerConfig.getName() != null)
? providerConfig.getName()
: versionedConnector.getName();
@@ -801,6 +808,10 @@ public class StandardConnectorRepository implements
ConnectorRepository {
final List<VersionedConfigurationStep> workingFlowConfiguration =
config.getWorkingFlowConfiguration();
if (workingFlowConfiguration != null) {
+ // Enrich provider-sourced SECRET_REFERENCE values with providerId
before they are
+ // converted into the in-memory ConnectorValueReference graph.
+ resolveSecretReferencesFromProvider(workingFlowConfiguration);
+
final MutableConnectorConfigurationContext workingConfigContext =
connector.getWorkingFlowContext().getConfigurationContext();
for (final VersionedConfigurationStep step :
workingFlowConfiguration) {
final StepConfiguration stepConfiguration =
toStepConfiguration(step);
@@ -929,4 +940,78 @@ public class StandardConnectorRepository implements
ConnectorRepository {
versionedReference.getSecretName(),
versionedReference.getFullyQualifiedSecretName());
};
}
+
+ /**
+ * Resolves the {@code providerId} on SECRET_REFERENCE entries that arrive
from a
+ * {@link ConnectorConfigurationProvider} with only {@code providerName}
populated. Provider
+ * implementations may legitimately omit {@code providerId} because it is
a runtime-assigned
+ * UUID; the framework alone has the {@link FlowManager} state needed to
map a provider name
+ * back to its identifier. Other fields (such as {@code secretName}) are
the responsibility of
+ * the provider and pass through unchanged.
+ *
+ * <p>This is invoked at the two boundaries where provider-sourced
configuration enters the
+ * framework (the working config returned by {@link
ConnectorConfigurationProvider#load(String)}
+ * and the working config returned by {@link
ConnectorConfigurationProvider#getSyncDirective}).</p>
+ *
+ * <p>Mutates the supplied references in place. References that already
have a non-null
+ * {@code providerId} are left alone. References whose {@code
providerName} cannot be
+ * unambiguously resolved are also left with a {@code null} {@code
providerId} so that the UI
+ * can surface the SECRET_REFERENCE as invalid; see {@link
#findProviderIdByName(String)}.</p>
+ *
+ * @param steps the configuration steps loaded from the provider; may be
{@code null} or empty
+ */
+ private void resolveSecretReferencesFromProvider(final
List<VersionedConfigurationStep> steps) {
+ if (steps == null) {
+ return;
+ }
+ logger.debug("Resolving SECRET_REFERENCE providerIds across {}
configuration step(s)", steps.size());
+ steps.stream()
+ .filter(Objects::nonNull)
+ .map(VersionedConfigurationStep::getProperties)
+ .filter(Objects::nonNull)
+ .flatMap(properties -> properties.values().stream())
+ .filter(Objects::nonNull)
+ .filter(ref ->
ConnectorValueType.SECRET_REFERENCE.name().equals(ref.getValueType()))
+ .filter(ref -> ref.getProviderId() == null)
+ .filter(ref -> ref.getProviderName() != null)
+ .forEach(ref ->
ref.setProviderId(findProviderIdByName(ref.getProviderName())));
+ }
+
+ /**
+ * Resolves a parameter provider id by name, but only when the name
unambiguously identifies a
+ * single provider in the current flow. If zero or more than one parameter
provider matches the
+ * given name, returns {@code null} so that the SECRET_REFERENCE will be
surfaced as invalid in
+ * the UI and the user can re-configure it explicitly.
+ *
+ * <p>Package-private for testing. Production callers should access this
method via
+ * {@link #resolveSecretReferencesFromProvider}.</p>
+ *
+ * @param providerName the parameter provider name to resolve; may be
{@code null}
+ * @return the matching parameter provider identifier when exactly one
provider has the given
+ * name; {@code null} when {@code providerName} is {@code null},
no provider matches,
+ * or multiple providers share the name
+ */
+ String findProviderIdByName(final String providerName) {
+ if (flowManager == null || providerName == null) {
+ return null;
+ }
+ final List<String> matches = new ArrayList<>();
+ for (final ParameterProviderNode parameterProviderNode :
flowManager.getAllParameterProviders()) {
+ if (providerName.equals(parameterProviderNode.getName())) {
+ matches.add(parameterProviderNode.getIdentifier());
+ }
+ }
+ if (matches.isEmpty()) {
+ logger.warn("No parameter provider found with name [{}];
SECRET_REFERENCE providerId will remain null", providerName);
+ return null;
+ }
+ if (matches.size() > 1) {
+ logger.warn("Multiple ({}) parameter providers found with name
[{}] (ids={}); SECRET_REFERENCE providerId cannot be unambiguously resolved and
will remain null",
+ matches.size(), providerName, matches);
+ return null;
+ }
+ final String resolvedId = matches.getFirst();
+ logger.debug("Resolved parameter provider name [{}] to id [{}]",
providerName, resolvedId);
+ return resolvedId;
+ }
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
index f6e50200852..0bcfd2736b2 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
@@ -19,6 +19,7 @@ package org.apache.nifi.components.connector;
import org.apache.nifi.asset.Asset;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.controller.ParameterProviderNode;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ScheduledState;
@@ -1170,6 +1171,75 @@ public class TestStandardConnectorRepository {
verify(connector).inheritConfiguration(any(), any(), any());
}
+ // --- findProviderIdByName ---
+
+ @Test
+ public void testFindProviderIdByNameResolvesUniqueMatch() {
+ final ParameterProviderNode snowflake =
mockParameterProvider("Snowflake Provider", "id-1");
+ final ParameterProviderNode vault = mockParameterProvider("Vault
Provider", "id-2");
+ final FlowManager flowManager = mock(FlowManager.class);
+
when(flowManager.getAllParameterProviders()).thenReturn(Set.of(snowflake,
vault));
+
+ final StandardConnectorRepository repository =
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class),
flowManager);
+
+ assertEquals("id-1", repository.findProviderIdByName("Snowflake
Provider"));
+ assertEquals("id-2", repository.findProviderIdByName("Vault
Provider"));
+ }
+
+ @Test
+ public void testFindProviderIdByNameReturnsNullWhenNoMatch() {
+ final ParameterProviderNode snowflake =
mockParameterProvider("Snowflake Provider", "id-1");
+ final FlowManager flowManager = mock(FlowManager.class);
+
when(flowManager.getAllParameterProviders()).thenReturn(Set.of(snowflake));
+
+ final StandardConnectorRepository repository =
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class),
flowManager);
+
+ assertNull(repository.findProviderIdByName("Nonexistent Provider"));
+ }
+
+ @Test
+ public void testFindProviderIdByNameReturnsNullWhenAmbiguousMatch() {
+ // Two parameter providers share the same name; the resolution must be
refused so that the
+ // resulting SECRET_REFERENCE is surfaced as invalid in the UI rather
than silently bound
+ // to whichever provider happens to come first.
+ final ParameterProviderNode dup1 = mockParameterProvider("Duplicate",
"id-a");
+ final ParameterProviderNode dup2 = mockParameterProvider("Duplicate",
"id-b");
+ final FlowManager flowManager = mock(FlowManager.class);
+ when(flowManager.getAllParameterProviders()).thenReturn(Set.of(dup1,
dup2));
+
+ final StandardConnectorRepository repository =
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class),
flowManager);
+
+ assertNull(repository.findProviderIdByName("Duplicate"));
+ }
+
+ @Test
+ public void testFindProviderIdByNameReturnsNullForNullInput() {
+ final ParameterProviderNode snowflake =
mockParameterProvider("Snowflake Provider", "id-1");
+ final FlowManager flowManager = mock(FlowManager.class);
+
when(flowManager.getAllParameterProviders()).thenReturn(Set.of(snowflake));
+
+ final StandardConnectorRepository repository =
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class),
flowManager);
+
+ assertNull(repository.findProviderIdByName(null));
+ }
+
+ @Test
+ public void testFindProviderIdByNameReturnsNullWhenNoProvidersRegistered()
{
+ final FlowManager flowManager = mock(FlowManager.class);
+ when(flowManager.getAllParameterProviders()).thenReturn(Set.of());
+
+ final StandardConnectorRepository repository =
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class),
flowManager);
+
+ assertNull(repository.findProviderIdByName("Anything"));
+ }
+
+ private static ParameterProviderNode mockParameterProvider(final String
name, final String id) {
+ final ParameterProviderNode node = mock(ParameterProviderNode.class);
+ when(node.getName()).thenReturn(name);
+ when(node.getIdentifier()).thenReturn(id);
+ return node;
+ }
+
// --- Helper Methods ---
private StandardConnectorRepository
createRepositoryWithProviderAndAssetManager(