Skip to content

Commit

Permalink
fix: 🐛 configuration file wasn't detecting changes properly (#244)
Browse files Browse the repository at this point in the history
When the configuration file was the only change, it wasn't triggering
any file changes detection

Refs: #238
  • Loading branch information
MaethorNaur committed Oct 19, 2020
1 parent 9d4e694 commit 17abb1b
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 269 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,14 @@ Example:

```yaml
name: "Test"
useProxy: false
specifications:
- "foo-service.yaml"
- "/openapi/bar-service.yaml"
- name: "Name used for this file"
path: "foobar.yaml"
useProxy: true
openapi:
useProxy: false
specifications:
- "foo-service.yaml"
- "/openapi/bar-service.yaml"
- name: "Name used for this file"
path: "foobar.yaml"
useProxy: true
grpc:
servers:
- address: 127.0.0.1
Expand All @@ -352,7 +353,6 @@ grpc:
# This list can be a mixed of string (path)
# or an object:
# name: Name of this service
# path: Path of files
# useProxy: activate the proxy for the interface. Otherwise your service might needs to activate CORS
specifications = []
```
Expand Down
177 changes: 100 additions & 77 deletions providers/git/src/main/scala/tech/unisonui/providers/git/git/Git.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import akka.stream.scaladsl.{
Source => AkkaSource
}
import cats.syntax.either._
import cats.syntax.option._
import com.typesafe.scalalogging.LazyLogging
import io.circe.generic.auto._
import io.circe.yaml.parser
Expand Down Expand Up @@ -82,49 +83,85 @@ object Git extends LazyLogging {
private val findSpecificationFiles
: Flow[FilesWithSha, FilesWithShaWithEvent] = AkkaFlow[FilesWithSha].map {
case ((repository, files), sha1) =>
val (repositoryWithNewPath, rootUseProxy, grpcSettings) =
findRestUIConfig(repository.directory.get.toPath)
.fold(
(repository,
Option.empty[Boolean],
Map.empty[String, ProtobufSetting])) {
case RestUI(serviceName,
specificationPaths,
maybeGrpcSettings,
useProxy) =>
val grpcSettings =
maybeGrpcSettings.fold(Map.empty[String, ProtobufSetting]) {
settings =>
settings.protobufFiles.view.mapValues {
case setting @ ProtobufSetting(_, servers)
if servers.isEmpty =>
setting.copy(servers = settings.servers)
case setting => setting
}.toMap
}
(repository.copy(specificationPaths = specificationPaths,
serviceName = serviceName),
useProxy,
grpcSettings)
}

val repoPath = repository.directory.get.toPath
val toAdd = filterSpecificationsFiles(repositoryWithNewPath,
files,
grpcSettings,
rootUseProxy)
val toDelete = repository.specificationPaths.filter { spec =>
!repositoryWithNewPath.specificationPaths.exists(newSpec =>
newSpec.path == spec.path)
}.map { spec =>
spec.path
.pipe(repoPath.resolve)
.normalize
.pipe(GitFileEvent.Deleted)
}
val events = toDelete ++ toAdd
(repositoryWithNewPath -> events -> sha1)
repoPath
.pipe(resolveConfigurationPath)
.fold(repository -> List.empty[GitFileEvent] -> sha1) { configPath =>
val repositoryWithNewPath =
loadConfigurationFile(configPath)
.fold(repository) {
case Configuration(serviceName, maybeOpenApi, maybeGrpc) =>
val openApiSpecs = maybeOpenApi.map {
case OpenApiSetting(specifcations, useProxy) =>
specifcations.map {
case spec: UnnamedOpenApi =>
spec.copy(useProxy = useProxy.some)
case spec: NamedOpenApi =>
spec.copy(useProxy =
spec.useProxy.orElse(useProxy.some))
}
}.getOrElse(Nil)
val grpcSpecs = maybeGrpc.map {
case GrpcSetting(servers, protobufFiles) =>
protobufFiles.map {
case (path, ProtobufSetting(name, protobufServers)) =>
val finalServers =
if (protobufServers.isEmpty) servers
else protobufServers
Grpc(name, path, finalServers)
}
}.getOrElse(Nil)
repository.copy(specifications = openApiSpecs ++ grpcSpecs,
serviceName = serviceName)
}
val changedFiles =
detectConfigurationFileChange(files,
configPath,
repository,
repositoryWithNewPath)
val toAdd =
filterSpecificationsFiles(repositoryWithNewPath, changedFiles)
val toDelete = repository.specifications.filter { spec =>
!repositoryWithNewPath.specifications.exists(newSpec =>
newSpec.path == spec.path)
}.map { spec =>
spec.path
.pipe(repoPath.resolve)
.normalize
.pipe(GitFileEvent.Deleted)
}
val events = toDelete ++ toAdd
(repositoryWithNewPath -> events -> sha1)
}
}
private def detectConfigurationFileChange(
currentChangedFiles: List[(Option[String], Path)],
configPath: Path,
oldRepository: Repository,
newRepository: Repository): List[(Option[String], Path)] =
if (currentChangedFiles.exists { case (_, file) =>
file.compareTo(configPath) == 0
}) {
val unchangedSpecifications = oldRepository.specifications.filter {
spec =>
newRepository.specifications.exists(newSpec =>
newSpec.path == spec.path)
}
Files
.walk(oldRepository.directory.get.toPath)
.iterator
.asScala
.to(LazyList)
.filter(Files.isRegularFile(_))
.filterNot(file =>
unchangedSpecifications.exists(spec =>
spec.path
.pipe(oldRepository.directory.get.toPath.resolve)
.normalize
.pipe(file.normalize.startsWith)))
.map(path => None -> path.normalize)
.toList
} else currentChangedFiles

private val latestSha1: Flow[FilesWithShaWithEvent, FilesWithShaWithEvent] =
AkkaFlow[FilesWithShaWithEvent].flatMapConcat {
Expand All @@ -148,14 +185,8 @@ object Git extends LazyLogging {
fromSource(
cacheDuration,
AkkaSource(repositories.collect {
case RepositorySettings(Location.Uri(uri),
branch,
specificationPaths,
useProxy) =>
Repository(uri,
branch.getOrElse(DefaultBranch),
specificationPaths.map(UnnamedSpecification(_)),
useProxy = useProxy)
case RepositorySettings(Location.Uri(uri), branch) =>
Repository(uri, branch.getOrElse(DefaultBranch))
})
)

Expand Down Expand Up @@ -208,18 +239,22 @@ object Git extends LazyLogging {
cwd: Option[File] = None): Source[Either[String, List[String]]] =
AkkaSource.single(ProcessArgs(GitCmd :: args, cwd)).via(Process.execute)

private def findRestUIConfig(path: Path): Option[RestUI] =
private def resolveConfigurationPath(path: Path): Option[Path] =
Try {
val unisonuiYamlPath = path.resolve(".unisonui.yaml")
val restuiYamlPath = path.resolve(".restui.yaml")
val configPath =
if (unisonuiYamlPath.toFile().exists()) unisonuiYamlPath
else restuiYamlPath
if (unisonuiYamlPath.toFile().exists()) unisonuiYamlPath.some
else if (restuiYamlPath.toFile().exists()) restuiYamlPath.some
else None
}.toOption.flatten

private def loadConfigurationFile(configPath: Path): Option[Configuration] =
Try {
val yaml =
new String(Files.readAllBytes(configPath), StandardCharsets.UTF_8)
parser
.parse(yaml)
.flatMap(_.as[RestUI])
.flatMap(_.as[Configuration])
.valueOr(throw _)
} match {
case Success(config) => Some(config)
Expand All @@ -231,30 +266,19 @@ object Git extends LazyLogging {

private def filterSpecificationsFiles(
repo: Repository,
files: List[(Option[String], Path)],
grpcSettings: Map[String, ProtobufSetting],
rootUseProxy: Option[Boolean]): List[GitFileEvent] = {
files: List[(Option[String], Path)]): List[GitFileEvent] = {
val repoPath = repo.directory.get.toPath
val specificationPaths = repo.specificationPaths.map {
case UnnamedSpecification(path) =>
(None, repoPath.resolve(path).normalize, rootUseProxy)
case NamedSpecification(name, path, useProxy) =>
(Some(name),
repoPath.resolve(path).normalize,
useProxy.orElse(rootUseProxy))
}
files.collect {
Function.unlift { case (_, file) =>
specificationPaths.find { case (_, specificationPath, _) =>
file.startsWith(specificationPath)
}.map { case (name, _, useProxy) =>
GitFileEvent.UpsertedOpenApi(name, file, useProxy)
}.orElse {
grpcSettings.find { case (path, _) =>
path.pipe(repoPath.resolve).normalize.pipe(file.startsWith)
}.map { case (_, ProtobufSetting(name, servers)) =>
GitFileEvent.UpsertedGrpc(name, file, servers)
}
repo.specifications.find { spec =>
spec.path.pipe(repoPath.resolve).normalize.pipe(file.startsWith)
}.map {
case spec: OpenApi =>
GitFileEvent.UpsertedOpenApi(spec.serviceName,
file,
spec.useProxy.getOrElse(false))
case spec: Grpc =>
GitFileEvent.UpsertedGrpc(spec.serviceName, file, spec.servers)
}
}
}
Expand All @@ -270,13 +294,12 @@ object Git extends LazyLogging {
AkkaSource(files)
.flatMapConcat(loadFile(_).async)
.map {
case LoadedContent.OpenApi(maybeName, path, content, maybeUseProxy) =>
case LoadedContent.OpenApi(maybeName, path, content, useProxy) =>
val serviceName =
maybeName.getOrElse(repo.serviceName.getOrElse(nameFromUri))
val filePath = repo.directory.get.toPath.relativize(path).toString
val id = s"$nameFromUri:$filePath"
val provider = uri.authority.host.address.split('.').head
val useProxy = maybeUseProxy.getOrElse(repo.useProxy)
val metadata =
Map(
Metadata.Provider -> provider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package tech.unisonui.providers.git.git.data

import io.circe.generic.auto._
import io.circe.{Decoder, HCursor}
import tech.unisonui.models.Service

final case class Configuration(name: Option[String],
openapi: Option[OpenApiSetting],
grpc: Option[GrpcSetting])

final case class OpenApiSetting(specifications: List[OpenApi],
useProxy: Boolean)

final case class GrpcSetting(servers: Map[String, Service.Grpc.Server],
protobufFiles: Map[String, ProtobufSetting])

final case class ProtobufSetting(maybeName: Option[String],
servers: Map[String, Service.Grpc.Server])
object Configuration {
implicit val decoder: Decoder[Configuration] = (cursor: HCursor) =>
for {
name <- cursor.get[Option[String]]("name")
openapi <- cursor.get[Option[OpenApiSetting]]("openapi")
specifications <- cursor.get[Option[List[OpenApi]]]("specifications")
grpc <- cursor.get[Option[GrpcSetting]]("grpc")
useProxy <- cursor.getOrElse[Boolean]("useProxy")(false)
openapiSettings = openapi
.orElse(specifications.map {
OpenApiSetting(_, useProxy)
})
} yield Configuration(name, openapiSettings, grpc)
}

object OpenApiSetting {
implicit val decoder: Decoder[OpenApiSetting] = (cursor: HCursor) =>
for {
specifications <- cursor.get[List[OpenApi]]("specifications")
useProxy <- cursor.getOrElse[Boolean]("useProxy")(false)
} yield OpenApiSetting(specifications, useProxy)
}

object GrpcSetting {
import ProtobufSetting.serverDecoder
implicit val decoder: Decoder[GrpcSetting] = (cursor: HCursor) =>
for {
servers <- cursor
.getOrElse[List[(String, Service.Grpc.Server)]]("servers")(Nil)
protobufFiles <- cursor.getOrElse[Map[String, ProtobufSetting]](
"protobufs")(Map.empty)
} yield GrpcSetting(servers.toMap, protobufFiles)
}

object ProtobufSetting {
implicit val decoder: Decoder[ProtobufSetting] = (cursor: HCursor) =>
for {
maybeName <- cursor.get[Option[String]]("name")
servers <- cursor
.getOrElse[List[(String, Service.Grpc.Server)]]("servers")(Nil)
} yield ProtobufSetting(maybeName, servers.toMap)

implicit val serverDecoder: Decoder[(String, Service.Grpc.Server)] =
(cursor: HCursor) =>
for {
address <- cursor.get[String]("address")
port <- cursor.get[Int]("port")
useTls <- cursor.getOrElse[Boolean]("useTls")(false)
name <- cursor.getOrElse[String]("name")(s"$address:$port")
} yield name -> Service.Grpc.Server(address, port, useTls)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tech.unisonui.providers.git.git.data
import cats.syntax.functor._
import cats.syntax.option._
import io.circe.{Decoder, HCursor}
import tech.unisonui.models.Service

sealed trait Specification extends Product with Serializable {
val serviceName: Option[String]
val path: String
}

sealed trait OpenApi extends Product with Serializable with Specification {
val useProxy: Option[Boolean]
}

final case class UnnamedOpenApi(path: String, useProxy: Option[Boolean] = None)
extends OpenApi {
val serviceName: Option[String] = None
}

final case class NamedOpenApi(name: String,
path: String,
useProxy: Option[Boolean])
extends OpenApi { val serviceName: Option[String] = name.some }

final case class Grpc(serviceName: Option[String],
path: String,
servers: Map[String, Service.Grpc.Server])
extends Specification

object OpenApi {
implicit val decoder: Decoder[OpenApi] =
List[Decoder[OpenApi]](Decoder[UnnamedOpenApi].widen,
Decoder[NamedOpenApi].widen).reduceLeft(_ or _)
}

object UnnamedOpenApi {
implicit val decoder: Decoder[UnnamedOpenApi] = (cursor: HCursor) =>
cursor.as[String].map(UnnamedOpenApi(_))
}

object NamedOpenApi {
implicit val decoder: Decoder[NamedOpenApi] = (cursor: HCursor) =>
for {
name <- cursor.get[String]("name")
path <- cursor.get[String]("path")
useProxy <- cursor.get[Option[Boolean]]("useProxy")
} yield NamedOpenApi(name, path, useProxy)
}

0 comments on commit 17abb1b

Please sign in to comment.