@@ -20,55 +20,104 @@ package com.expedia.www.haystack.collector.commons.config
2020import java .io .File
2121import java .util .Properties
2222
23- import com .typesafe .config .{Config , ConfigFactory }
23+ import com .typesafe .config .{Config , ConfigFactory , ConfigRenderOptions , ConfigValueType }
2424import org .apache .kafka .clients .producer .ProducerConfig
2525import org .apache .kafka .clients .producer .ProducerConfig .{KEY_SERIALIZER_CLASS_CONFIG , VALUE_SERIALIZER_CLASS_CONFIG }
2626import org .apache .kafka .common .serialization .ByteArraySerializer
27+ import org .slf4j .LoggerFactory
2728
2829import scala .collection .JavaConversions ._
30+ import scala .collection .JavaConverters ._
2931
3032object ConfigurationLoader {
3133
32- private val ENV_NAME_PREFIX = " HAYSTACK_PROP_"
34+ private val LOGGER = LoggerFactory .getLogger(ConfigurationLoader .getClass)
35+
36+ private [haystack] val ENV_NAME_PREFIX = " HAYSTACK_PROP_"
3337
3438 /**
3539 * Load and return the configuration
3640 * if overrides_config_path env variable exists, then we load that config file and use base conf as fallback,
3741 * else we load the config from env variables(prefixed with haystack) and use base conf as fallback
3842 *
43+ * @param resourceName name of the resource file to be loaded. Default value is `config/base.conf`
44+ * @param envNamePrefix env variable prefix to override config values. Default is `HAYSTACK_PROP_`
45+ *
46+ * @return an instance of com.typesafe.Config
3947 */
40- lazy val loadAppConfig : Config = {
41- val baseConfig = ConfigFactory .load(" config/base.conf" )
42-
43- sys.env.get(" HAYSTACK_OVERRIDES_CONFIG_PATH" ) match {
44- case Some (path) => ConfigFactory .parseFile(new File (path)).withFallback(baseConfig)
45- case _ => loadFromEnvVars().withFallback(baseConfig)
48+ def loadConfigFileWithEnvOverrides (resourceName : String = " config/base.conf" ,
49+ envNamePrefix : String = ENV_NAME_PREFIX ) : Config = {
50+
51+ require(resourceName != null && resourceName.length > 0 , " resourceName is required" )
52+ require(envNamePrefix != null && envNamePrefix.length > 0 , " envNamePrefix is required" )
53+
54+ val baseConfig = ConfigFactory .load(resourceName)
55+
56+ val keysWithArrayValues = baseConfig.entrySet()
57+ .asScala
58+ .filter(_.getValue.valueType() == ConfigValueType .LIST )
59+ .map(_.getKey)
60+ .toSet
61+
62+ val config = sys.env.get(" HAYSTACK_OVERRIDES_CONFIG_PATH" ) match {
63+ case Some (overrideConfigPath) =>
64+ val overrideConfig = ConfigFactory .parseFile(new File (overrideConfigPath))
65+ ConfigFactory
66+ .parseMap(parsePropertiesFromMap(sys.env, keysWithArrayValues, envNamePrefix).asJava)
67+ .withFallback(overrideConfig)
68+ .withFallback(baseConfig)
69+ .resolve()
70+ case _ => ConfigFactory
71+ .parseMap(parsePropertiesFromMap(sys.env, keysWithArrayValues, envNamePrefix).asJava)
72+ .withFallback(baseConfig)
73+ .resolve()
4674 }
75+
76+ // In key-value pairs that contain 'password' in the key, replace the value with asterisks
77+ LOGGER .info(config.root()
78+ .render(ConfigRenderOptions .defaults().setOriginComments(false ))
79+ .replaceAll(" (?i)(\\\" .*password\\\"\\ s*:\\ s*)\\\" .+\\\" " , " $1********" ))
80+
81+ config
4782 }
4883
4984 /**
50- * @return new config object with haystack specific environment variables
85+ * @return new config object with haystack specific environment variables
5186 */
52- private def loadFromEnvVars (): Config = {
53- val envMap = sys.env.filter {
54- case (envName, _) => isHaystackEnvVar(envName)
87+ private [haystack] def parsePropertiesFromMap (envVars : Map [String , String ],
88+ keysWithArrayValues : Set [String ],
89+ envNamePrefix : String ): Map [String , Object ] = {
90+ envVars.filter {
91+ case (envName, _) => envName.startsWith(envNamePrefix)
5592 } map {
56- case (envName, envValue) => (transformEnvVarName(envName), envValue)
93+ case (envName, envValue) =>
94+ val key = transformEnvVarName(envName, envNamePrefix)
95+ if (keysWithArrayValues.contains(key)) (key, transformEnvVarArrayValue(envValue)) else (key, envValue)
5796 }
58-
59- ConfigFactory .parseMap(envMap)
6097 }
6198
62- private def isHaystackEnvVar (env : String ): Boolean = env.startsWith(ENV_NAME_PREFIX )
63-
6499 /**
65100 * converts the env variable to HOCON format
66101 * for e.g. env variable HAYSTACK_KAFKA_STREAMS_NUM_STREAM_THREADS gets converted to kafka.streams.num.stream.threads
67102 * @param env environment variable name
68- * @return
103+ * @return variable name that complies with hocon key
69104 */
70- private def transformEnvVarName (env : String ): String = {
71- env.replaceFirst(ENV_NAME_PREFIX , " " ).toLowerCase.replace(" _" , " ." )
105+ private def transformEnvVarName (env : String , envNamePrefix : String ): String = {
106+ env.replaceFirst(envNamePrefix, " " ).toLowerCase.replace(" _" , " ." )
107+ }
108+
109+ /**
110+ * converts the env variable value to iterable object if it starts and ends with '[' and ']' respectively.
111+ * @param env environment variable value
112+ * @return string or iterable object
113+ */
114+ private def transformEnvVarArrayValue (env : String ): java.util.List [String ] = {
115+ if (env.startsWith(" [" ) && env.endsWith(" ]" )) {
116+ import scala .collection .JavaConverters ._
117+ env.substring(1 , env.length - 1 ).split(',' ).filter(str => (str != null ) && str.nonEmpty).toList.asJava
118+ } else {
119+ throw new RuntimeException (" config key is of array type, so it should start and end with '[', ']' respectively" )
120+ }
72121 }
73122
74123 def kafkaProducerConfig (config : Config ): KafkaProduceConfiguration = {
@@ -97,6 +146,5 @@ object ConfigurationLoader {
97146 def extractorConfiguration (config : Config ): ExtractorConfiguration = {
98147 val extractor = config.getConfig(" extractor" )
99148 ExtractorConfiguration (outputFormat = if (extractor.hasPath(" output.format" )) Format .withName(extractor.getString(" output.format" )) else Format .PROTO )
100-
101149 }
102150}
0 commit comments