This is an automated email from the ASF dual-hosted git repository.
thenatog 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 518f413d9f NIFI-9637: Adding GcpSecretManagerParameterProvider
518f413d9f is described below
commit 518f413d9fa12e7fdf62c7ef33aefe33d1a2d596
Author: Joe Gresock <[email protected]>
AuthorDate: Sat Sep 10 10:35:21 2022 -0400
NIFI-9637: Adding GcpSecretManagerParameterProvider
Signed-off-by: Nathan Gough <[email protected]>
This closes #6394.
---
.../nifi-gcp-bundle/nifi-gcp-nar/pom.xml | 5 +
.../nifi-gcp-parameter-providers/pom.xml | 84 +++++++++
.../gcp/GcpSecretManagerParameterProvider.java | 200 ++++++++++++++++++++
.../org.apache.nifi.parameter.ParameterProvider | 16 ++
.../additionalDetails.html | 55 ++++++
.../gcp/TestGcpSecretManagerParameterProvider.java | 208 +++++++++++++++++++++
.../org.mockito.plugins.MockMaker | 15 ++
nifi-nar-bundles/nifi-gcp-bundle/pom.xml | 1 +
8 files changed, 584 insertions(+)
diff --git a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-nar/pom.xml
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-nar/pom.xml
index 168756d348..b1f69f73b8 100644
--- a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-nar/pom.xml
+++ b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-nar/pom.xml
@@ -41,5 +41,10 @@
<artifactId>nifi-gcp-processors</artifactId>
<version>1.18.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-gcp-parameter-providers</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ </dependency>
</dependencies>
</project>
diff --git
a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/pom.xml
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/pom.xml
new file mode 100644
index 0000000000..91ac70afa5
--- /dev/null
+++ b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-gcp-bundle</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-gcp-parameter-providers</artifactId>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-utils</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-gcp-services-api</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.cloud</groupId>
+ <artifactId>google-cloud-secretmanager</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.google.auth</groupId>
+ <artifactId>google-auth-library-oauth2-http</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-expression-language</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-mock</artifactId>
+ <version>1.18.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git
a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/java/org/apache/nifi/parameter/gcp/GcpSecretManagerParameterProvider.java
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/java/org/apache/nifi/parameter/gcp/GcpSecretManagerParameterProvider.java
new file mode 100644
index 0000000000..19ffa06c56
--- /dev/null
+++
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/java/org/apache/nifi/parameter/gcp/GcpSecretManagerParameterProvider.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.parameter.gcp;
+
+import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse;
+import com.google.cloud.secretmanager.v1.ProjectName;
+import com.google.cloud.secretmanager.v1.Secret;
+import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
+import
com.google.cloud.secretmanager.v1.SecretManagerServiceClient.ListSecretsPage;
+import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
+import com.google.cloud.secretmanager.v1.SecretVersionName;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.gcp.credentials.service.GCPCredentialsService;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.parameter.AbstractParameterProvider;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterDescriptor;
+import org.apache.nifi.parameter.ParameterGroup;
+import org.apache.nifi.parameter.VerifiableParameterProvider;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Reads secrets from GCP Secret Manager to provide parameter values. Secrets
must be created similar to the following GCP cli command: <br/><br/>
+ * <code>gcp secretsmanager create-secret --name "[Context]" --secret-string
'{ "[Param]": "[secretValue]", "[Param2]": "[secretValue2]" }'</code> <br/><br/>
+ *
+ */
+
+@Tags({"gcp", "secret", "manager"})
+@CapabilityDescription("Fetches parameters from GCP Secret Manager. Each
secret becomes a Parameter, which can be mapped to a Parameter Group " +
+ "by adding a GCP label named 'group-name'.")
+public class GcpSecretManagerParameterProvider extends
AbstractParameterProvider implements VerifiableParameterProvider {
+ private static final Logger logger =
LoggerFactory.getLogger(GcpSecretManagerParameterProvider.class);
+
+ public static final PropertyDescriptor GROUP_NAME_PATTERN = new
PropertyDescriptor.Builder()
+ .name("group-name-pattern")
+ .displayName("Group Name Pattern")
+ .description("A Regular Expression matching on the 'group-name'
label value that identifies Secrets whose parameters should be fetched. " +
+ "Any secrets without a 'group-name' label value that
matches this Regex will not be fetched.")
+ .addValidator(StandardValidators.REGULAR_EXPRESSION_VALIDATOR)
+ .required(true)
+ .defaultValue(".*")
+ .build();
+
+ public static final PropertyDescriptor PROJECT_ID = new PropertyDescriptor
+ .Builder().name("gcp-project-id")
+ .displayName("Project ID")
+ .description("Google Cloud Project ID")
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .required(true)
+ .build();
+
+ /**
+ * Links to the {@link GCPCredentialsService} which provides credentials
for this particular processor.
+ */
+ public static final PropertyDescriptor GCP_CREDENTIALS_PROVIDER_SERVICE =
new PropertyDescriptor.Builder()
+ .name("gcp-credentials-provider-service")
+ .displayName("GCP Credentials Provider Service")
+ .description("The Controller Service used to obtain Google Cloud
Platform credentials.")
+ .required(true)
+ .identifiesControllerService(GCPCredentialsService.class)
+ .build();
+
+ private static final String GROUP_NAME_LABEL = "group-name";
+ private static final String SECRETS_PATH = "secrets/";
+ private static final List<PropertyDescriptor> PROPERTIES =
Collections.unmodifiableList(Arrays.asList(
+ GROUP_NAME_PATTERN,
+ PROJECT_ID,
+ GCP_CREDENTIALS_PROVIDER_SERVICE
+ ));
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return PROPERTIES;
+ }
+
+ @Override
+ public List<ParameterGroup> fetchParameters(final ConfigurationContext
context) throws IOException {
+ final Map<String, ParameterGroup> providedParameterGroups = new
HashMap<>();
+ final SecretManagerServiceClient secretsManager =
this.configureClient(context);
+ final ProjectName projectName =
ProjectName.of(context.getProperty(PROJECT_ID).getValue());
+
+ final SecretManagerServiceClient.ListSecretsPagedResponse
pagedResponse = secretsManager.listSecrets(projectName);
+ ListSecretsPage page = pagedResponse.getPage();
+ do {
+ for (final Secret secret : page.getValues()) {
+ final String contextName =
secret.getLabelsOrDefault(GROUP_NAME_LABEL, null);
+ if (contextName == null) {
+ getLogger().debug("Secret [{}] does not have the {} label,
and will be skipped", secret.getName(), GROUP_NAME_LABEL);
+ continue;
+ }
+ final String secretName =
StringUtils.substringAfter(secret.getName(), SECRETS_PATH);
+
+ fetchSecret(secretsManager, context, secretName, contextName,
providedParameterGroups);
+ }
+ if (page.hasNextPage()) {
+ page = page.getNextPage();
+ }
+ } while (page.hasNextPage());
+ return new ArrayList<>(providedParameterGroups.values());
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext
context, final ComponentLog verificationLogger) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ try {
+ final List<ParameterGroup> parameterGroups =
fetchParameters(context);
+ int count = 0;
+ for (final ParameterGroup group : parameterGroups) {
+ count += group.getParameters().size();
+ }
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.SUCCESSFUL)
+ .verificationStepName("Fetch Parameters")
+ .explanation(String.format("Fetched secret keys [%d] as
parameters within groups [%d]",
+ count, parameterGroups.size()))
+ .build());
+ } catch (final Exception e) {
+ verificationLogger.error("Failed to fetch parameters", e);
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(ConfigVerificationResult.Outcome.FAILED)
+ .verificationStepName("Fetch Parameters")
+ .explanation("Failed to fetch parameters: " +
e.getMessage())
+ .build());
+ }
+ return results;
+ }
+
+ private void fetchSecret(final SecretManagerServiceClient secretsManager,
final ConfigurationContext context, final String secretName,
+ final String groupName,
final Map<String, ParameterGroup> providedParameterGroups) {
+ final Pattern groupNamePattern =
Pattern.compile(context.getProperty(GROUP_NAME_PATTERN).getValue());
+ final String projectId = context.getProperty(PROJECT_ID).getValue();
+
+ if (!groupNamePattern.matcher(groupName).matches()) {
+ logger.debug("Secret [{}] label [{}] does not match the group name
pattern {}", secretName, groupName, groupNamePattern);
+ return;
+ }
+
+ final SecretVersionName secretVersionName =
SecretVersionName.of(projectId, secretName, "latest");
+
+ // Access the secret version.
+ final AccessSecretVersionResponse response =
secretsManager.accessSecretVersion(secretVersionName);
+ final String parameterValue =
response.getPayload().getData().toStringUtf8();
+
+ final Parameter parameter = createParameter(secretName,
parameterValue);
+
+ if (parameter != null) {
+ final ParameterGroup group = providedParameterGroups
+ .computeIfAbsent(groupName, key -> new
ParameterGroup(groupName, new ArrayList<>()));
+ final List<Parameter> updatedParameters = new
ArrayList<>(group.getParameters());
+ updatedParameters.add(parameter);
+ providedParameterGroups.put(groupName, new
ParameterGroup(groupName, updatedParameters));
+ }
+ }
+
+ private Parameter createParameter(final String parameterName, final String
parameterValue) {
+ final ParameterDescriptor parameterDescriptor = new
ParameterDescriptor.Builder().name(parameterName).build();
+ return new Parameter(parameterDescriptor, parameterValue, null, true);
+ }
+
+ SecretManagerServiceClient configureClient(final ConfigurationContext
context) throws IOException {
+ final GCPCredentialsService credentialsService =
context.getProperty(GCP_CREDENTIALS_PROVIDER_SERVICE).asControllerService(GCPCredentialsService.class);
+
+ final SecretManagerServiceClient client =
SecretManagerServiceClient.create(SecretManagerServiceSettings
+ .newBuilder().setCredentialsProvider(() ->
credentialsService.getGoogleCredentials())
+ .build());
+
+ return client;
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/resources/META-INF/services/org.apache.nifi.parameter.ParameterProvider
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/resources/META-INF/services/org.apache.nifi.parameter.ParameterProvider
new file mode 100644
index 0000000000..29aa40d470
--- /dev/null
+++
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/resources/META-INF/services/org.apache.nifi.parameter.ParameterProvider
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.nifi.parameter.gcp.GcpSecretManagerParameterProvider
+
diff --git
a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.gcp.GcpSecretManagerParameterProvider/additionalDetails.html
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.gcp.GcpSecretManagerParameterProvider/additionalDetails.html
new file mode 100644
index 0000000000..bc32d72cb4
--- /dev/null
+++
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/main/resources/docs/org.apache.nifi.parameter.gcp.GcpSecretManagerParameterProvider/additionalDetails.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<head>
+ <meta charset="utf-8" />
+ <title>GcpSecretManagerParameterProvider</title>
+
+ <link rel="stylesheet" href="../../../../../css/component-usage.css"
type="text/css" />
+</head>
+<body>
+ <h3>Mapping GCP Secrets to Parameter Contexts</h3>
+
+ <p>
+ The GcpSecretManagerParameterProvider maps a Secret to a Parameter,
which can be grouped by adding a "group-name" label.
+ To create a compatible secret from the GCP Console:
+ </p>
+
+ <ol>
+ <li>From the Secret Manager service, click the "Create Secret"
button</li>
+ <li>Enter the Secret name. This is the name of a parameter. Enter a
value.</li>
+ <li>Under "Labels", add a label with a Key of "group-name" and a value
of the intended Parameter Group name.</li>
+ </ol>
+
+ <p>
+ Alternatively, from the command line, run a command like the following:
+ </p>
+
+ <pre>
+printf "[Parameter Value]" | gcloud secrets create
--labels=group-name="[Parameter Group Name]" "[Parameter Name]" --data-file=-
+ </pre>
+
+ <p>In this example, [Parameter Group Name] should be the intended name of
the Parameter Group, [Parameter Name] should be
+ the parameter name, and [Parameter Value] should be the value of the
parameter.</p>
+
+ <h3>Configuring the Parameter Provider</h3>
+
+ <p>
+ GCP Secrets must be explicitly matched in the "Group Name Pattern"
property in order for them to be fetched. This
+ prevents more than the intended Secrets from being pulled into NiFi.
+ </p>
+</body>
+</html>
diff --git
a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/test/java/org/apache/nifi/parameter/gcp/TestGcpSecretManagerParameterProvider.java
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/test/java/org/apache/nifi/parameter/gcp/TestGcpSecretManagerParameterProvider.java
new file mode 100644
index 0000000000..e4983e0ff6
--- /dev/null
+++
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/test/java/org/apache/nifi/parameter/gcp/TestGcpSecretManagerParameterProvider.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.parameter.gcp;
+
+import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse;
+import com.google.cloud.secretmanager.v1.ProjectName;
+import com.google.cloud.secretmanager.v1.Secret;
+import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
+import
com.google.cloud.secretmanager.v1.SecretManagerServiceClient.ListSecretsPage;
+import
com.google.cloud.secretmanager.v1.SecretManagerServiceClient.ListSecretsPagedResponse;
+import com.google.cloud.secretmanager.v1.SecretPayload;
+import com.google.cloud.secretmanager.v1.SecretVersionName;
+import com.google.protobuf.ByteString;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterDescriptor;
+import org.apache.nifi.parameter.ParameterGroup;
+import org.apache.nifi.parameter.VerifiableParameterProvider;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.util.MockComponentLog;
+import org.apache.nifi.util.MockConfigurationContext;
+import org.apache.nifi.util.MockParameterProviderInitializationContext;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+public class TestGcpSecretManagerParameterProvider {
+
+ final List<Parameter> mySecretParameters = Arrays.asList(
+ parameter("paramA", "valueA"),
+ parameter("paramB", "valueB"),
+ parameter("otherC", "valueOther"),
+ parameter("paramD", "valueD"),
+ parameter("nonSensitiveE", "valueE"),
+ parameter("otherF", "valueF")
+ );
+ final List<Parameter> otherSecretParameters = Arrays.asList(
+ parameter("paramG", "valueG"),
+ parameter("otherH", "valueOther")
+ );
+ final List<Parameter> unrelatedParameters =
Collections.singletonList(parameter("paramK", "unused"));
+ final List<ParameterGroup> mockParameterGroups = Arrays.asList(
+ new ParameterGroup("MySecret", mySecretParameters),
+ new ParameterGroup("OtherSecret", otherSecretParameters),
+ new ParameterGroup("Unrelated", unrelatedParameters) // will not
be picked up
+ );
+
+ @Test
+ public void testFetchParametersWithNoSecrets() throws
InitializationException, IOException {
+ final List<ParameterGroup> expectedGroups =
Collections.singletonList(new ParameterGroup("MySecret",
Collections.emptyList()));
+ runProviderTest(mockSecretManagerClient(expectedGroups), 0,
ConfigVerificationResult.Outcome.SUCCESSFUL);
+ }
+
+ @Test
+ public void testFetchParameters() throws InitializationException,
IOException {
+ runProviderTest(mockSecretManagerClient(mockParameterGroups), 8,
ConfigVerificationResult.Outcome.SUCCESSFUL);
+ }
+
+ @Test
+ public void testFetchParametersListFailure() throws
InitializationException, IOException {
+ final SecretManagerServiceClient mockSecretsManager =
mock(SecretManagerServiceClient.class);
+
when(mockSecretsManager.listSecrets(any(ProjectName.class))).thenThrow(new
RuntimeException("Fake exception"));
+ runProviderTest(mockSecretsManager, 0,
ConfigVerificationResult.Outcome.FAILED);
+ }
+
+ @Test
+ public void testFetchParametersGetSecretFailure() throws
InitializationException, IOException {
+ final SecretManagerServiceClient mockSecretsManager =
mock(SecretManagerServiceClient.class);
+ final ListSecretsPagedResponse listSecretsPagedResponse =
mock(ListSecretsPagedResponse.class);
+ final ListSecretsPage page = mock(ListSecretsPage.class);
+ when(listSecretsPagedResponse.getPage()).thenReturn(page);
+
+ when(page.hasNextPage()).thenReturn(false);
+ final Secret secret = mock(Secret.class);
+ when(secret.getName()).thenReturn("paramA");
+ when(secret.getLabelsOrDefault("group-name",
null)).thenReturn("Secret");
+ when(page.getValues()).thenReturn(Collections.singletonList(secret));
+
+
when(mockSecretsManager.accessSecretVersion(any(SecretVersionName.class))).thenThrow(new
RuntimeException("Fake exception"));
+ runProviderTest(mockSecretsManager, 0,
ConfigVerificationResult.Outcome.FAILED);
+ }
+
+ private GcpSecretManagerParameterProvider getParameterProvider() {
+ return spy(new GcpSecretManagerParameterProvider());
+ }
+
+ private SecretManagerServiceClient mockSecretManagerClient(final
List<ParameterGroup> mockGroups) {
+ final SecretManagerServiceClient secretManager =
mock(SecretManagerServiceClient.class);
+
+ final ListSecretsPagedResponse listSecretsPagedResponse =
mock(ListSecretsPagedResponse.class);
+
when(secretManager.listSecrets(any(ProjectName.class))).thenReturn(listSecretsPagedResponse);
+
+ boolean mockedFirstPage = false;
+ ListSecretsPage currentPage;
+ ListSecretsPage previousPage = null;
+ for (final Iterator<ParameterGroup> it = mockGroups.iterator();
it.hasNext(); ) {
+ final ParameterGroup group = it.next();
+ currentPage = mock(ListSecretsPage.class);
+ if (mockedFirstPage) {
+ when(previousPage.getNextPage()).thenReturn(currentPage);
+ } else {
+
when(listSecretsPagedResponse.getPage()).thenReturn(currentPage);
+ mockedFirstPage = true;
+ }
+ final List<Secret> values = new ArrayList<>();
+ values.addAll(group.getParameters().stream()
+ .map(parameter -> mockSecret(secretManager,
group.getGroupName(), parameter))
+ .collect(Collectors.toList()));
+ when(currentPage.getValues()).thenReturn(values);
+ if (it.hasNext()) {
+ when(currentPage.hasNextPage()).thenReturn(true);
+ previousPage = currentPage;
+ } else {
+ when(currentPage.hasNextPage()).thenReturn(false);
+ }
+ }
+
+ return secretManager;
+ }
+
+ final Secret mockSecret(final SecretManagerServiceClient secretManager,
final String groupName, final Parameter parameter) {
+ final Secret secret = mock(Secret.class);
+
+ final String parameterName = parameter.getDescriptor().getName();
+ when(secret.getName()).thenReturn("projects/project/secrets/" +
parameterName);
+ when(secret.getLabelsOrDefault("group-name",
null)).thenReturn(groupName);
+
+ final AccessSecretVersionResponse response =
mock(AccessSecretVersionResponse.class);
+
doReturn(response).when(secretManager).accessSecretVersion(argThat((SecretVersionName
secretVersionName) -> secretVersionName.getSecret().equals(parameterName)));
+ final SecretPayload payload = mock(SecretPayload.class);
+ final ByteString data = ByteString.copyFromUtf8(parameter.getValue());
+ when(payload.getData()).thenReturn(data);
+ when(response.getPayload()).thenReturn(payload);
+ return secret;
+ }
+
+ private List<ParameterGroup> runProviderTest(final
SecretManagerServiceClient secretsManager, final int expectedCount,
+ final
ConfigVerificationResult.Outcome expectedOutcome) throws
InitializationException, IOException {
+
+ final GcpSecretManagerParameterProvider parameterProvider =
getParameterProvider();
+
doReturn(secretsManager).when(parameterProvider).configureClient(any());
+ final MockParameterProviderInitializationContext initContext = new
MockParameterProviderInitializationContext("id", "name",
+ new MockComponentLog("providerId", parameterProvider));
+ parameterProvider.initialize(initContext);
+
+ final Map<PropertyDescriptor, String> properties = new HashMap<>();
+ properties.put(GcpSecretManagerParameterProvider.GROUP_NAME_PATTERN,
".*Secret");
+ properties.put(GcpSecretManagerParameterProvider.PROJECT_ID,
"my-project");
+ final MockConfigurationContext mockConfigurationContext = new
MockConfigurationContext(properties, null);
+
+ List<ParameterGroup> parameterGroups = new ArrayList<>();
+ // Verify parameter fetching
+ if (expectedOutcome == ConfigVerificationResult.Outcome.FAILED) {
+ assertThrows(RuntimeException.class, () ->
parameterProvider.fetchParameters(mockConfigurationContext));
+ } else {
+ parameterGroups =
parameterProvider.fetchParameters(mockConfigurationContext);
+ final int count = (int) parameterGroups.stream()
+ .flatMap(group -> group.getParameters().stream())
+ .count();
+ assertEquals(expectedCount, count);
+ }
+
+ // Verify config verification
+ final List<ConfigVerificationResult> results =
((VerifiableParameterProvider)
parameterProvider).verify(mockConfigurationContext, initContext.getLogger());
+
+ assertEquals(1, results.size());
+ assertEquals(expectedOutcome, results.get(0).getOutcome());
+
+ return parameterGroups;
+ }
+
+ private static Parameter parameter(final String name, final String value) {
+ return new Parameter(new
ParameterDescriptor.Builder().name(name).build(), value);
+ }
+
+}
diff --git
a/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000..dc5c0f7aa2
--- /dev/null
+++
b/nifi-nar-bundles/nifi-gcp-bundle/nifi-gcp-parameter-providers/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+mock-maker-inline
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-gcp-bundle/pom.xml
b/nifi-nar-bundles/nifi-gcp-bundle/pom.xml
index 59633ada9d..67f62059ad 100644
--- a/nifi-nar-bundles/nifi-gcp-bundle/pom.xml
+++ b/nifi-nar-bundles/nifi-gcp-bundle/pom.xml
@@ -45,6 +45,7 @@
<module>nifi-gcp-services-api</module>
<module>nifi-gcp-services-api-nar</module>
<module>nifi-gcp-processors</module>
+ <module>nifi-gcp-parameter-providers</module>
<module>nifi-gcp-nar</module>
</modules>
</project>