blob: 22d30ebe4e2a89cd8936ef0aa7b5592c3e35400f [file] [log] [blame]
/*
* 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.stateless.config;
import org.apache.nifi.stateless.engine.StatelessEngineConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PropertiesFileEngineConfigurationParser {
private static final Logger logger = LoggerFactory.getLogger(PropertiesFileEngineConfigurationParser.class);
private static final String PREFIX = "nifi.stateless.";
private static final String NAR_DIRECTORY = PREFIX + "nar.directory";
private static final String EXTENSIONS_DIRECTORY = PREFIX + "extensions.directory";
private static final String WORKING_DIRECTORY = PREFIX + "working.directory";
private static final String CONTENT_REPO_DIRECTORY = PREFIX + "content.repository.directory";
private static final String TRUSTSTORE_FILE = PREFIX + "security.truststore";
private static final String TRUSTSTORE_TYPE = PREFIX + "security.truststoreType";
private static final String TRUSTSTORE_PASSWORD = PREFIX + "security.truststorePasswd";
private static final String KEYSTORE_FILE = PREFIX + "security.keystore";
private static final String KEYSTORE_TYPE = PREFIX + "security.keystoreType";
private static final String KEYSTORE_PASSWORD = PREFIX + "security.keystorePasswd";
private static final String KEY_PASSWORD = PREFIX + "security.keyPasswd";
private static final String SENSITIVE_PROPS_KEY = PREFIX + "sensitive.props.key";
private static final String KRB5_FILE = PREFIX + "kerberos.krb5.file";
private static final String DEFAULT_KRB5_FILENAME = "/etc/krb5.conf";
private static final String DEFAULT_ENCRYPTION_PASSWORD = "nifi-stateless";
private static final Pattern EXTENSION_CLIENT_PATTERN = Pattern.compile("\\Qnifi.stateless.extension.client.\\E(.*?)\\.(.+)");
public StatelessEngineConfiguration parseEngineConfiguration(final File propertiesFile) throws IOException, StatelessConfigurationException {
if (!propertiesFile.exists()) {
throw new FileNotFoundException("Could not find properties file " + propertiesFile.getAbsolutePath());
}
final Properties properties = new Properties();
try (final InputStream in = new FileInputStream(propertiesFile)) {
properties.load(in);
}
final File narDirectory = new File(getRequired(properties, NAR_DIRECTORY));
if (!narDirectory.exists()) {
throw new StatelessConfigurationException("NAR Directory " + narDirectory.getAbsolutePath() + " specified in properties file does not exist");
}
final File workingDirectory = new File(getRequired(properties, WORKING_DIRECTORY));
if (!workingDirectory.exists() && !workingDirectory.mkdirs()) {
throw new StatelessConfigurationException("Working Directory " + workingDirectory.getAbsolutePath() + " specified in properties file does not exist and could not be created");
}
final String extensionsDirectoryFilename = properties.getProperty(EXTENSIONS_DIRECTORY);
final File extensionsDirectory = extensionsDirectoryFilename == null ? narDirectory : new File(extensionsDirectoryFilename);
if (!extensionsDirectory.exists() && !extensionsDirectory.mkdirs()) {
throw new StatelessConfigurationException("Extensions Directory " + narDirectory.getAbsolutePath() + " specified in properties file does not exist and could not be created");
}
final String contentRepoDirectoryFilename = properties.getProperty(CONTENT_REPO_DIRECTORY, "");
final File contentRepoDirectory = contentRepoDirectoryFilename.isEmpty() ? null : new File(contentRepoDirectoryFilename);
final String krb5Filename = properties.getProperty(KRB5_FILE, DEFAULT_KRB5_FILENAME);
final File krb5File = new File(krb5Filename);
final String sensitivePropsKey = properties.getProperty(SENSITIVE_PROPS_KEY, DEFAULT_ENCRYPTION_PASSWORD);
final SslContextDefinition sslContextDefinition = parseSslContextDefinition(properties);
final List<ExtensionClientDefinition> extensionClients = parseExtensionClients(properties);
return new StatelessEngineConfiguration() {
@Override
public File getWorkingDirectory() {
return workingDirectory;
}
@Override
public File getNarDirectory() {
return narDirectory;
}
@Override
public File getExtensionsDirectory() {
return extensionsDirectory;
}
@Override
public File getKrb5File() {
return krb5File;
}
@Override
public Optional<File> getContentRepositoryDirectory() {
return Optional.ofNullable(contentRepoDirectory);
}
@Override
public SslContextDefinition getSslContext() {
return sslContextDefinition;
}
@Override
public String getSensitivePropsKey() {
return sensitivePropsKey;
}
@Override
public List<ExtensionClientDefinition> getExtensionClients() {
return extensionClients;
}
};
}
private List<ExtensionClientDefinition> parseExtensionClients(final Properties properties) {
final Map<String, ExtensionClientDefinition> extensionClientDefinitions = new LinkedHashMap<>();
for (final String propertyName : properties.stringPropertyNames()) {
final Matcher matcher = EXTENSION_CLIENT_PATTERN.matcher(propertyName);
if (!matcher.matches()) {
continue;
}
// For a property name like:
// nifi.stateless.extension.client.c1.type=nexus
// We consider 'c1' the <client key> and 'type' the <relative property name>
final String clientKey = matcher.group(1);
final ExtensionClientDefinition definition = extensionClientDefinitions.computeIfAbsent(clientKey, key -> new ExtensionClientDefinition());
final String relativePropertyName = matcher.group(2);
final String propertyValue = properties.getProperty(propertyName);
switch (relativePropertyName) {
case "type":
definition.setExtensionClientType(propertyValue);
break;
case "baseUrl":
definition.setBaseUrl(propertyValue);
break;
case "timeout":
definition.setCommsTimeout(propertyValue);
break;
case "useSslContext":
definition.setUseSslContext(Boolean.parseBoolean(propertyValue));
break;
default:
logger.warn("Encountered invalid property: <{}>. Will ignore this property.", propertyName);
break;
}
}
return new ArrayList<>(extensionClientDefinitions.values());
}
private SslContextDefinition parseSslContextDefinition(final Properties properties) {
final String truststoreFile = properties.getProperty(TRUSTSTORE_FILE);
if (truststoreFile == null || truststoreFile.trim().isEmpty()) {
return null;
}
final SslContextDefinition sslContextDefinition;
sslContextDefinition = new SslContextDefinition();
sslContextDefinition.setTruststoreFile(truststoreFile);
sslContextDefinition.setTruststorePass(properties.getProperty(TRUSTSTORE_PASSWORD));
sslContextDefinition.setTruststoreType(properties.getProperty(TRUSTSTORE_TYPE));
final String keystoreFile = properties.getProperty(KEYSTORE_FILE);
if (keystoreFile != null && !keystoreFile.trim().isEmpty()) {
sslContextDefinition.setKeystoreFile(keystoreFile);
sslContextDefinition.setKeystoreType(properties.getProperty(KEYSTORE_TYPE));
final String keystorePass = properties.getProperty(KEYSTORE_PASSWORD);
sslContextDefinition.setKeystorePass(keystorePass);
final String explicitKeyPass = properties.getProperty(KEY_PASSWORD);
final String keyPass = (explicitKeyPass == null || explicitKeyPass.trim().isEmpty()) ? keystorePass : explicitKeyPass;
sslContextDefinition.setKeyPass(keyPass);
}
return sslContextDefinition;
}
private String getRequired(final Properties properties, final String key) throws StatelessConfigurationException {
final String propertyValue = properties.getProperty(key);
if (propertyValue == null || propertyValue.trim().isEmpty()) {
throw new StatelessConfigurationException("Properties file is missing required property " + key);
}
return propertyValue.trim();
}
}