NIFI-9637: Adding GcpSecretManagerParameterProvider

Signed-off-by: Nathan Gough <thenatog@gmail.com>

This closes #6394.
This commit is contained in:
Joe Gresock 2022-09-10 10:35:21 -04:00 committed by Nathan Gough
parent 8148588c9c
commit 518f413d9f
8 changed files with 584 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>