@@ -28,12 +28,14 @@ import com.monovore.decline.{Command, Opts}
28
28
import cats .syntax .either ._
29
29
import cats .syntax .validated ._
30
30
31
- import com .amazonaws .regions .DefaultAwsRegionProviderChain
31
+ import com .amazonaws .regions .{ DefaultAwsRegionProviderChain , Regions }
32
32
33
- import pureconfig .{CamelCase , ConfigFieldMapping , ConfigObjectSource , ConfigReader , ConfigSource }
33
+ import com .typesafe .config .ConfigOrigin
34
+
35
+ import pureconfig ._
34
36
import pureconfig .generic .{FieldCoproductHint , ProductHint }
35
37
import pureconfig .generic .semiauto ._
36
- import pureconfig .error .{ ConfigReaderFailures , FailureReason }
38
+ import pureconfig .error ._
37
39
38
40
object Config {
39
41
@@ -68,7 +70,7 @@ object Config {
68
70
initialPosition : String ,
69
71
initialTimestamp : Option [String ],
70
72
maxRecords : Long ,
71
- region : Option [ String ] ,
73
+ region : Region ,
72
74
appName : String ,
73
75
customEndpoint : Option [String ],
74
76
dynamodbCustomEndpoint : Option [String ],
@@ -91,15 +93,6 @@ object Config {
91
93
92
94
object Kinesis {
93
95
final case class Buffer (byteLimit : Long , recordLimit : Long , timeLimit : Long )
94
-
95
- implicit val sourceKinesisConfigReader : ConfigReader [Kinesis ] =
96
- deriveReader[Kinesis ].emap { c =>
97
- val region = c.region.orElse(getRegion)
98
- region match {
99
- case Some (_) => c.copy(region = region).asRight
100
- case _ => RawFailureReason (" Region isn't set in the Kinesis source" ).asLeft
101
- }
102
- }
103
96
}
104
97
}
105
98
@@ -131,17 +124,7 @@ object Config {
131
124
ssl : Boolean
132
125
)
133
126
134
- final case class ESAWS (signing : Boolean , region : Option [String ])
135
- object ESAWS {
136
- implicit val sinkGoodESAWSConfigReader : ConfigReader [ESAWS ] =
137
- deriveReader[ESAWS ].emap { c =>
138
- val region = c.region.orElse(getRegion)
139
- if (c.signing && region.isEmpty)
140
- RawFailureReason (" Region needs to be set when AWS signing is true" ).asLeft
141
- else
142
- c.copy(region = region).asRight
143
- }
144
- }
127
+ final case class ESAWS (signing : Boolean , region : Region )
145
128
146
129
final case class ESCluster (index : String , documentType : Option [String ])
147
130
@@ -163,19 +146,9 @@ object Config {
163
146
164
147
final case class Kinesis (
165
148
streamName : String ,
166
- region : Option [ String ] ,
149
+ region : Region ,
167
150
customEndpoint : Option [String ]
168
151
) extends BadSink
169
- object Kinesis {
170
- implicit val sinkBadKinesisConfigReader : ConfigReader [Kinesis ] =
171
- deriveReader[Kinesis ].emap { c =>
172
- val region = c.region.orElse(getRegion)
173
- region match {
174
- case Some (_) => c.copy(region = region).asRight
175
- case _ => RawFailureReason (" Region isn't set in the Kinesis sink" ).asLeft
176
- }
177
- }
178
- }
179
152
}
180
153
}
181
154
@@ -208,48 +181,81 @@ object Config {
208
181
final case class Metrics (cloudWatch : Boolean )
209
182
}
210
183
211
- final case class RawFailureReason ( description : String ) extends FailureReason
184
+ final case class Region ( name : String )
212
185
213
- implicit val streamLoaderConfigReader : ConfigReader [StreamLoaderConfig ] =
214
- deriveReader[StreamLoaderConfig ]
215
- implicit val sourceConfigReader : ConfigReader [Source ] =
216
- deriveReader[Source ]
217
- implicit val sourceStdinConfigReader : ConfigReader [Source .Stdin .type ] =
218
- deriveReader[Source .Stdin .type ]
219
- implicit val sourceNsqConfigReader : ConfigReader [Source .Nsq ] =
220
- deriveReader[Source .Nsq ]
221
- implicit val sourceNsqBufferConfigReader : ConfigReader [Source .Nsq .Buffer ] =
222
- deriveReader[Source .Nsq .Buffer ]
223
- implicit val sourceKinesisConfigBufferReader : ConfigReader [Source .Kinesis .Buffer ] =
224
- deriveReader[Source .Kinesis .Buffer ]
225
- implicit val sinkConfigReader : ConfigReader [Sink ] =
226
- deriveReader[Sink ]
227
- implicit val sinkGoodConfigReader : ConfigReader [Sink .GoodSink ] =
228
- deriveReader[Sink .GoodSink ]
229
- implicit val sinkGoodStdoutConfigReader : ConfigReader [Sink .GoodSink .Stdout .type ] =
230
- deriveReader[Sink .GoodSink .Stdout .type ]
231
- implicit val sinkGoodESConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch ] =
232
- deriveReader[Sink .GoodSink .Elasticsearch ]
233
- implicit val sinkGoodESClientConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch .ESClient ] =
234
- deriveReader[Sink .GoodSink .Elasticsearch .ESClient ]
235
- implicit val sinkGoodESClusterConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch .ESCluster ] =
236
- deriveReader[Sink .GoodSink .Elasticsearch .ESCluster ]
237
- implicit val sinkGoodESChunkConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch .ESChunk ] =
238
- deriveReader[Sink .GoodSink .Elasticsearch .ESChunk ]
239
- implicit val sinkBadSinkConfigReader : ConfigReader [Sink .BadSink ] =
240
- deriveReader[Sink .BadSink ]
241
- implicit val sinkBadNoneConfigReader : ConfigReader [Sink .BadSink .None .type ] =
242
- deriveReader[Sink .BadSink .None .type ]
243
- implicit val sinkBadStderrConfigReader : ConfigReader [Sink .BadSink .Stderr .type ] =
244
- deriveReader[Sink .BadSink .Stderr .type ]
245
- implicit val sinkBadNsqConfigReader : ConfigReader [Sink .BadSink .Nsq ] =
246
- deriveReader[Sink .BadSink .Nsq ]
247
- implicit val monitoringConfigReader : ConfigReader [Monitoring ] =
248
- deriveReader[Monitoring ]
249
- implicit val snowplowMonitoringConfig : ConfigReader [Monitoring .SnowplowMonitoring ] =
250
- deriveReader[Monitoring .SnowplowMonitoring ]
251
- implicit val metricsConfigReader : ConfigReader [Monitoring .Metrics ] =
252
- deriveReader[Monitoring .Metrics ]
186
+ final case class RawFailureReason (description : String ) extends FailureReason
187
+ final case class RawConfigReaderFailure (description : String , origin : Option [ConfigOrigin ] = None )
188
+ extends ConfigReaderFailure
189
+
190
+ case class implicits (
191
+ regionConfigReader : ConfigReader [Region ] with ReadsMissingKeys = new ConfigReader [Region ]
192
+ with ReadsMissingKeys {
193
+ override def from (cur : ConfigCursor ) =
194
+ if (cur.isUndefined)
195
+ Config .getRegion.toRight(
196
+ ConfigReaderFailures (
197
+ RawConfigReaderFailure (
198
+ " Region can not be resolved, needs to be passed explicitly"
199
+ )
200
+ )
201
+ )
202
+ else
203
+ cur.asString.flatMap { r =>
204
+ val region = Region (r)
205
+ checkRegion(region).leftMap(e => ConfigReaderFailures (RawConfigReaderFailure (e)))
206
+ }
207
+ }
208
+ ) {
209
+ implicit val implRegionConfigReader : ConfigReader [Region ] = regionConfigReader
210
+ implicit val streamLoaderConfigReader : ConfigReader [StreamLoaderConfig ] =
211
+ deriveReader[StreamLoaderConfig ]
212
+ implicit val sourceConfigReader : ConfigReader [Source ] =
213
+ deriveReader[Source ]
214
+ implicit val sourceStdinConfigReader : ConfigReader [Source .Stdin .type ] =
215
+ deriveReader[Source .Stdin .type ]
216
+ implicit val sourceNsqConfigReader : ConfigReader [Source .Nsq ] =
217
+ deriveReader[Source .Nsq ]
218
+ implicit val sourceNsqBufferConfigReader : ConfigReader [Source .Nsq .Buffer ] =
219
+ deriveReader[Source .Nsq .Buffer ]
220
+ implicit val sourceKinesisConfigReader : ConfigReader [Source .Kinesis ] =
221
+ deriveReader[Source .Kinesis ]
222
+ implicit val sourceKinesisConfigBufferReader : ConfigReader [Source .Kinesis .Buffer ] =
223
+ deriveReader[Source .Kinesis .Buffer ]
224
+ implicit val sinkConfigReader : ConfigReader [Sink ] =
225
+ deriveReader[Sink ]
226
+ implicit val sinkGoodConfigReader : ConfigReader [Sink .GoodSink ] =
227
+ deriveReader[Sink .GoodSink ]
228
+ implicit val sinkGoodStdoutConfigReader : ConfigReader [Sink .GoodSink .Stdout .type ] =
229
+ deriveReader[Sink .GoodSink .Stdout .type ]
230
+ implicit val sinkGoodESConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch ] =
231
+ deriveReader[Sink .GoodSink .Elasticsearch ]
232
+ implicit val sinkGoodESClientConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch .ESClient ] =
233
+ deriveReader[Sink .GoodSink .Elasticsearch .ESClient ]
234
+ implicit val sinkGoodESClusterConfigReader : ConfigReader [
235
+ Sink .GoodSink .Elasticsearch .ESCluster
236
+ ] =
237
+ deriveReader[Sink .GoodSink .Elasticsearch .ESCluster ]
238
+ implicit val sinkGoodESAWSConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch .ESAWS ] =
239
+ deriveReader[Sink .GoodSink .Elasticsearch .ESAWS ]
240
+ implicit val sinkGoodESChunkConfigReader : ConfigReader [Sink .GoodSink .Elasticsearch .ESChunk ] =
241
+ deriveReader[Sink .GoodSink .Elasticsearch .ESChunk ]
242
+ implicit val sinkBadSinkConfigReader : ConfigReader [Sink .BadSink ] =
243
+ deriveReader[Sink .BadSink ]
244
+ implicit val sinkBadKinesisConfigReader : ConfigReader [Sink .BadSink .Kinesis ] =
245
+ deriveReader[Sink .BadSink .Kinesis ]
246
+ implicit val sinkBadNoneConfigReader : ConfigReader [Sink .BadSink .None .type ] =
247
+ deriveReader[Sink .BadSink .None .type ]
248
+ implicit val sinkBadStderrConfigReader : ConfigReader [Sink .BadSink .Stderr .type ] =
249
+ deriveReader[Sink .BadSink .Stderr .type ]
250
+ implicit val sinkBadNsqConfigReader : ConfigReader [Sink .BadSink .Nsq ] =
251
+ deriveReader[Sink .BadSink .Nsq ]
252
+ implicit val monitoringConfigReader : ConfigReader [Monitoring ] =
253
+ deriveReader[Monitoring ]
254
+ implicit val snowplowMonitoringConfig : ConfigReader [Monitoring .SnowplowMonitoring ] =
255
+ deriveReader[Monitoring .SnowplowMonitoring ]
256
+ implicit val metricsConfigReader : ConfigReader [Monitoring .Metrics ] =
257
+ deriveReader[Monitoring .Metrics ]
258
+ }
253
259
254
260
val config = Opts
255
261
.option[Path ](" config" , " Path to a HOCON configuration file" )
@@ -263,7 +269,16 @@ object Config {
263
269
264
270
val command = Command (" snowplow-stream-loader" , generated.Settings .version, true )(config)
265
271
266
- def parseConfig (arguments : Array [String ]): Either [String , StreamLoaderConfig ] =
272
+ def parseConfig (arguments : Array [String ]): Either [String , StreamLoaderConfig ] = {
273
+ val configImplicits = com.snowplowanalytics.stream.loader.Config .implicits()
274
+ parseConfig(arguments, configImplicits.streamLoaderConfigReader)
275
+ }
276
+
277
+ def parseConfig (
278
+ arguments : Array [String ],
279
+ configReader : ConfigReader [StreamLoaderConfig ]
280
+ ): Either [String , StreamLoaderConfig ] = {
281
+ implicit val implConfigReader : ConfigReader [StreamLoaderConfig ] = configReader
267
282
for {
268
283
path <- command.parse(arguments).leftMap(_.toString)
269
284
source = path.fold(ConfigSource .empty)(ConfigSource .file)
@@ -272,6 +287,7 @@ object Config {
272
287
)
273
288
parsed <- c.load[StreamLoaderConfig ].leftMap(showFailures)
274
289
} yield parsed
290
+ }
275
291
276
292
/** Optionally give precedence to configs wrapped in a "esloader" block. To help avoid polluting config namespace */
277
293
private def namespaced (configObjSource : ConfigObjectSource ): ConfigObjectSource =
@@ -295,8 +311,14 @@ object Config {
295
311
failureStrings.mkString(" \n " )
296
312
}
297
313
298
- private def getRegion : Option [String ] =
299
- Either .catchNonFatal((new DefaultAwsRegionProviderChain ).getRegion).toOption
314
+ private def getRegion : Option [Region ] =
315
+ Either .catchNonFatal((new DefaultAwsRegionProviderChain ).getRegion).toOption.map(Region )
316
+
317
+ private def checkRegion (region : Region ): Either [String , Region ] = {
318
+ val allRegions = Regions .values().toList.map(_.getName)
319
+ if (allRegions.contains(region.name)) region.asRight
320
+ else s " Region ${region.name} is unknown, choose from [ ${allRegions.mkString(" , " )}] " .asLeft
321
+ }
300
322
301
323
// Used as an option prefix when reading system properties.
302
324
val Namespace = " snowplow"
0 commit comments