package com.sky.kafka.configurator import cats.Eq import cats.data.Reader import cats.instances.int._ import cats.instances.vector._ import cats.instances.try_._ import cats.syntax.eq._ import com.sky.kafka.configurator.error.{ReplicationChangeFound, TopicNotFound} import com.typesafe.scalalogging.LazyLogging import scala.util.control.NonFatal import scala.util.{Failure, Success} case class TopicConfigurator(topicReader: TopicReader, topicWriter: TopicWriter) extends LazyLogging { def configure(topic: Topic): Logger[Unit] = topicReader.fetch(topic.name) match { case Success(currentTopic) => updateTopic(currentTopic, topic) case Failure(TopicNotFound(_)) => topicWriter.create(topic) .withLog(s"Topic ${topic.name} was not found, so it has been created") case Failure(NonFatal(t)) => Failure(t).asWriter } private def updateTopic(oldTopic: Topic, newTopic: Topic): Logger[Unit] = { def ifDifferent[T: Eq](oldValue: T, newValue: T)(updateOperation: (Topic, Topic) => Logger[Unit])(messageIfSame: String): Logger[Unit] = if (oldValue =!= newValue) updateOperation(oldTopic, newTopic) else Success(()).withLog(messageIfSame) import TopicConfigurator._ for { _ <- ifDifferent(oldTopic.replicationFactor, newTopic.replicationFactor)(failReplicationChange)(s"Replication factor unchanged for ${newTopic.name}.") _ <- ifDifferent(oldTopic.partitions, newTopic.partitions)(updatePartitions)(s"No change in number of partitions for ${newTopic.name}") _ <- ifDifferent(oldTopic.config, newTopic.config)(updateConfig)(s"No change in config for ${newTopic.name}") } yield () } private def failReplicationChange(oldTopic: Topic, newTopic: Topic): Logger[Unit] = Failure(ReplicationChangeFound).asWriter private def updatePartitions(oldTopic: Topic, newTopic: Topic): Logger[Unit] = topicWriter .updatePartitions(newTopic.name, newTopic.partitions) .withLog(s"Updated topic ${newTopic.name} from ${oldTopic.partitions} to ${newTopic.partitions} partition(s)") private def updateConfig(oldTopic: Topic, newTopic: Topic): Logger[Unit] = topicWriter .updateConfig(newTopic.name, newTopic.config) .withLog(s"Updated configuration of topic ${newTopic.name}") } object TopicConfigurator { def reader: Reader[AppConfig, TopicConfigurator] = KafkaTopicAdmin.reader .map(kafkaAdminClient => TopicConfigurator(kafkaAdminClient, kafkaAdminClient)) private implicit val topicConfigIsContained: Eq[Map[String, String]] = Eq.instance { case (left, right) => left.toList.forall(right.toList.contains(_)) || right.toList.forall(left.toList.contains(_)) } }