Skip to content

Commit

Permalink
feat: 🎸 enable disable proxy from config
Browse files Browse the repository at this point in the history
Enable the proxy using the configuration
  • Loading branch information
MaethorNaur committed Sep 29, 2020
1 parent 36935d2 commit 266c8f0
Show file tree
Hide file tree
Showing 24 changed files with 166 additions and 95 deletions.
9 changes: 8 additions & 1 deletion README.md
Expand Up @@ -95,6 +95,7 @@ restui {
port = "restui.specification.endpoint.port" // Label specifying the port on which the OpenApi spec is available.
service-name = "restui.specification.endpoint.service-name" // Label specifying the service name for RestUI.
specification-path = "restui.specification.endpoint.specification-path" // Label of the path where the OpenApi spec file is.
use-proxy = "restui.specification.endpoint.use-proxy" // Label of use to enable the usage of the proxy.
}
}
Expand All @@ -108,6 +109,7 @@ restui {
port = "restui.specification.endpoint.port" // Label specifying the port on which the OpenApi spec is available.
protocol = "restui.specification.endpoint.protocol" // Label specifying which protocol the OpenApi spec is exposed.
specification-path = "restui.specification.endpoint.specification-path" // Label of the path where the OpenApi spec file is.
use-proxy = "restui.specification.endpoint.use-proxy" // Label of use to enable the usage of the proxy.
}
}
Expand Down Expand Up @@ -300,6 +302,7 @@ The object follows this schema:
specification-paths = [] // List of OpenApi spec files or directories containing those kind of files
// inside your repository. Those paths are overrided by the restui configuration file inside
// of your repository.
use-proxy = no // Just enable the proxy or not for this repository
}
```

Expand All @@ -312,23 +315,27 @@ 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
```

```yaml
# Service's name.
# If this field does not provide the service name will be inferred from the repository URL
# Example: "https://github.com/MyOrg/MyRepo" -> "MyOrg/MyOrg"
name = "service name"
# name = "service name"
# useProxy activate the proxy for the interface. Otherwise your service might needs to activate CORS
# List of OpenApi spec files or directories
# 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
11 changes: 6 additions & 5 deletions core/src/main/scala/restui/models/Event.scala
Expand Up @@ -7,18 +7,19 @@ sealed trait Event extends Product with Serializable {

object Event {

final case class ServiceUp(id: String, name: String, metadata: Map[String, String] = Map.empty) extends Event
final case class ServiceDown(id: String) extends Event
final case class ServiceContentChanged(id: String) extends Event
final case class ServiceUp(id: String, name: String, useProxy: Boolean, metadata: Map[String, String] = Map.empty) extends Event
final case class ServiceDown(id: String) extends Event
final case class ServiceContentChanged(id: String) extends Event

implicit val encoder: Encoder[Event] = (event: Event) =>
event match {
case ServiceUp(id, name, metadata) =>
case ServiceUp(id, name, useProxy, metadata) =>
Json.obj(
"event" -> Json.fromString("serviceUp"),
"id" -> Json.fromString(id),
"name" -> Json.fromString(name),
"metadata" -> Json.obj(metadata.view.mapValues(Json.fromString).toSeq: _*)
"metadata" -> Json.obj(metadata.view.mapValues(Json.fromString).toSeq: _*),
"useProxy" -> Json.fromBoolean(useProxy)
)
case ServiceDown(id) =>
Json.obj(
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/scala/restui/models/Service.scala
@@ -1,3 +1,8 @@
package restui.models

final case class Service(id: String, name: String, file: String, metadata: Map[String, String] = Map.empty, hash: String = "")
final case class Service(id: String,
name: String,
file: String,
metadata: Map[String, String] = Map.empty,
useProxy: Boolean = false,
hash: String = "")
8 changes: 4 additions & 4 deletions core/src/test/scala/restui/models/EventSpec.scala
Expand Up @@ -10,8 +10,8 @@ class EventSpec extends AnyFlatSpec with Matchers with TableDrivenPropertyChecks
it should "serialise the event as a valid json string" in {
val properties = Table(
("event", "json"),
(Event.ServiceUp("id", "test", Map("key" -> "value")),
"""{"event":"serviceUp","id":"id","name":"test","metadata":{"key":"value"}}"""),
(Event.ServiceUp("id", "test", false, Map("key" -> "value")),
"""{"event":"serviceUp","id":"id","name":"test","metadata":{"key":"value"},"useProxy":false}"""),
(Event.ServiceDown("id"), """{"event":"serviceDown","id":"id"}""")
)

Expand All @@ -22,8 +22,8 @@ class EventSpec extends AnyFlatSpec with Matchers with TableDrivenPropertyChecks
it should "serialise a list of events as a valid json string" in {
val properties = Table(
("event", "json"),
(List(Event.ServiceUp("id", "test", Map("key" -> "value"))),
"""[{"event":"serviceUp","id":"id","name":"test","metadata":{"key":"value"}}]""")
(List(Event.ServiceUp("id", "test", false, Map("key" -> "value"))),
"""[{"event":"serviceUp","id":"id","name":"test","metadata":{"key":"value"},"useProxy":false}]""")
)

forAll(properties) { (events, json) =>
Expand Down
1 change: 1 addition & 0 deletions providers/docker/src/main/resources/reference.conf
Expand Up @@ -6,6 +6,7 @@ restui {
port = "restui.specification.endpoint.port"
service-name = "restui.specification.endpoint.service-name"
specification-path = "restui.specification.endpoint.specification-path"
use-proxy = "restui.specification.endpoint.use-proxy"
}
}
}
@@ -1,6 +1,7 @@
package restui.providers.docker

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

import akka.NotUsed
import akka.actor.ActorSystem
Expand Down Expand Up @@ -59,8 +60,8 @@ class DockerClient(private val client: HttpClient, private val settings: Setting
private def handleServiceUp(id: String): Source[ServiceEvent, NotUsed] =
container(id).async.flatMapConcat { container =>
findEndpoint(container.labels, container.ip).fold(Source.empty[ServiceEvent]) {
case (serviceName, address) =>
downloadFile(id, serviceName, address).async
case (serviceName, address, useProxy) =>
downloadFile(id, serviceName, address, useProxy).async
}
}

Expand All @@ -79,7 +80,7 @@ class DockerClient(private val client: HttpClient, private val settings: Setting
}
}.mapMaterializedValue(_ => NotUsed)

private def downloadFile(id: String, serviceName: String, uri: String): Source[ServiceEvent, NotUsed] =
private def downloadFile(id: String, serviceName: String, uri: String, useProxy: Boolean): Source[ServiceEvent, NotUsed] =
Source.futureSource {
client
.downloadFile(uri)
Expand All @@ -91,7 +92,7 @@ class DockerClient(private val client: HttpClient, private val settings: Setting

Source.single(
ServiceEvent.ServiceUp(
Service(id, serviceName, content, metadata)
Service(id, serviceName, content, metadata, useProxy = useProxy)
)
)
}
Expand All @@ -105,17 +106,19 @@ class DockerClient(private val client: HttpClient, private val settings: Setting
for {
labels <- findMatchingLabels(labels)
ipAddress <- maybeIpAddress
} yield (labels.serviceName, s"http://$ipAddress:${labels.port.toInt}${labels.specificationPath}")
useProxy = Try(labels.useProxy.toBoolean).getOrElse(false)
} yield (labels.serviceName, s"http://$ipAddress:${labels.port.toInt}${labels.specificationPath}", useProxy)

private def findMatchingLabels(labels: Map[String, String]): Option[Labels] =
for {
serviceName <- labels.get(settings.labels.serviceName)
port <- labels.get(settings.labels.port)
useProxy = labels.getOrElse(settings.labels.useProxy, "false")
specificationPath = labels.getOrElse(settings.labels.specificationPath, "/specification.yaml")
} yield Labels(serviceName, port, specificationPath)
} yield Labels(serviceName, port, specificationPath, useProxy)
}

object DockerClient {
private type ServiceNameWithAddress = (String, String)
private type ServiceNameWithAddress = (String, String, Boolean)
private val MaximumFrameSize: Int = 10000
}
Expand Up @@ -3,15 +3,17 @@ package restui.providers.docker
import com.typesafe.config.Config

final case class Settings(dockerHost: String, labels: Labels)
final case class Labels(serviceName: String, port: String, specificationPath: String)
final case class Labels(serviceName: String, port: String, specificationPath: String, useProxy: String)

// $COVERAGE-OFF$
object Settings {
private val Namespace = "restui.provider.docker"
def from(config: Config): Settings = {
val dockerHost = config.getString(s"$Namespace.host")
val serviceName = config.getString(s"$Namespace.labels.service-name")
val port = config.getString(s"$Namespace.labels.port")
val specificationPath = config.getString(s"$Namespace.labels.specification-path")
Settings(dockerHost, Labels(serviceName, port, specificationPath))
val useProxy = config.getString(s"$Namespace.labels.useProxy")
Settings(dockerHost, Labels(serviceName, port, specificationPath, useProxy))
}
}
Expand Up @@ -29,7 +29,7 @@ class DockerClientSpec
override def afterAll(): Unit =
TestKit.shutdownActorSystem(system)

private val settings = Settings("myDocker.sock", Labels("name", "port", "specification"))
private val settings = Settings("myDocker.sock", Labels("name", "port", "specification", "useProxy"))
private val MatchingContainerLabels = Map("name" -> ServiceName, "port" -> "9999", "specification" -> "/openapi.yaml")
private val NonMatchingContainerLabels = Map("name" -> ServiceName)

Expand Down
40 changes: 23 additions & 17 deletions providers/git/src/main/scala/restui/providers/git/git/Git.scala
Expand Up @@ -67,12 +67,15 @@ object Git extends LazyLogging {

private val findSpecificationFiles: Flow[FilesWithSha, FilesWithShaWithEvent] = AkkaFlow[FilesWithSha].map {
case ((repository, files), sha1) =>
val repositoryWithNewPath = findRestUIConfig(repository.directory.get.toPath).fold(repository) {
case RestUI(serviceName, specificationPaths) => repository.copy(specificationPaths = specificationPaths, serviceName = serviceName)
}
val (repositoryWithNewPath, rootUseProxy) =
findRestUIConfig(repository.directory.get.toPath).fold(repository -> Option.empty[Boolean]) {
case RestUI(serviceName, specificationPaths, useProxy) =>
repository.copy(specificationPaths = specificationPaths, serviceName = serviceName) -> useProxy
}

val repoPath = repository.directory.get.toPath

val toAdd = filterSpecificationsFiles(repositoryWithNewPath, files)
val toAdd = filterSpecificationsFiles(repositoryWithNewPath, files, rootUseProxy)
val toDelete = repository.specificationPaths.filter { spec =>
!repositoryWithNewPath.specificationPaths.exists(newSpec => newSpec.path == spec.path)
}.map { spec =>
Expand Down Expand Up @@ -102,8 +105,8 @@ object Git extends LazyLogging {
fromSource(
cacheDuration,
AkkaSource(repositories.collect {
case RepositorySettings(Location.Uri(uri), branch, specificationPaths) =>
Repository(uri, branch.getOrElse(DefaultBranch), specificationPaths.map(UnnamedSpecification(_)))
case RepositorySettings(Location.Uri(uri), branch, specificationPaths, useProxy) =>
Repository(uri, branch.getOrElse(DefaultBranch), specificationPaths.map(UnnamedSpecification(_)), useProxy = useProxy)
})
)

Expand Down Expand Up @@ -163,21 +166,23 @@ object Git extends LazyLogging {
None
}

private def filterSpecificationsFiles(repo: Repository, files: List[(Option[String], Path)]): List[GitFileEvent] = {
private def filterSpecificationsFiles(repo: Repository,
files: List[(Option[String], Path)],
rootUseProxy: Option[Boolean]): List[GitFileEvent] = {
val repoPath = repo.directory.get.toPath
val specificationPaths = repo.specificationPaths.map {
case UnnamedSpecification(path) => None -> repoPath.resolve(path).normalize
case NamedSpecification(name, path) => Some(name) -> repoPath.resolve(path).normalize
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) =>
case (_, specificationPath, _) =>
file.startsWith(specificationPath)
}.map {
case (name, _) =>
GitFileEvent.Upserted(name, file)
case (name, _, useProxy) =>
GitFileEvent.Upserted(name, file, useProxy)
}
}
}
Expand All @@ -192,17 +197,18 @@ object Git extends LazyLogging {
AkkaSource(files)
.flatMapConcat(loadFile(_).async)
.map {
case Right((maybeName, path, content)) =>
case Right((maybeName, path, content, maybeUseProxy)) =>
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,
Metadata.File -> filePath
)
ServiceEvent.ServiceUp(Service(id, serviceName, content, metadata))
ServiceEvent.ServiceUp(Service(id, serviceName, content, metadata, useProxy))
case Left(path) =>
val filePath = repo.directory.get.toPath.relativize(path).toString
val id = s"$nameFromUri:$filePath"
Expand All @@ -212,14 +218,14 @@ object Git extends LazyLogging {

}

private def loadFile(event: GitFileEvent): Source[Either[Path, (Option[String], Path, String)]] =
private def loadFile(event: GitFileEvent): Source[Either[Path, (Option[String], Path, String, Option[Boolean])]] =
event match {
case GitFileEvent.Deleted(path) =>
AkkaSource.single(Left(path))
case GitFileEvent.Upserted(maybeName, path) =>
case GitFileEvent.Upserted(maybeName, path, useProxy) =>
Try(new String(Files.readAllBytes(path), StandardCharsets.UTF_8)) match {
case Success(content) =>
AkkaSource.single(Right((maybeName, path, content)))
AkkaSource.single(Right((maybeName, path, content, useProxy)))
case Failure(exception) =>
logger.warn(s"Error while reading $path", exception)
AkkaSource.single(Left(path))
Expand Down
Expand Up @@ -12,27 +12,29 @@ package object data {
branch: String,
specificationPaths: List[Specification],
directory: Option[File] = None,
serviceName: Option[String] = None)
serviceName: Option[String] = None,
useProxy: Boolean = false)

trait Specification extends Product with Serializable {
val path: String
}
final case class UnnamedSpecification(path: String) extends Specification
final case class NamedSpecification(name: String, path: String) extends Specification
final case class RestUI(name: Option[String], specifications: List[Specification])
final case class UnnamedSpecification(path: String) extends Specification
final case class NamedSpecification(name: String, path: String, useProxy: Option[Boolean]) extends Specification
final case class RestUI(name: Option[String], specifications: List[Specification], useProxy: Option[Boolean])

trait GitFileEvent extends Product with Serializable
object GitFileEvent {
final case class Deleted(path: Path) extends GitFileEvent
final case class Upserted(maybeName: Option[String], path: Path) extends GitFileEvent
final case class Deleted(path: Path) extends GitFileEvent
final case class Upserted(maybeName: Option[String], path: Path, useProxy: Option[Boolean]) extends GitFileEvent
}

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

object Specification {
Expand All @@ -47,8 +49,9 @@ package object data {
object NamedSpecification {
implicit val decoder: Decoder[NamedSpecification] = (cursor: HCursor) =>
for {
name <- cursor.get[String]("name")
path <- cursor.get[String]("path")
} yield NamedSpecification(name, path)
name <- cursor.get[String]("name")
path <- cursor.get[String]("path")
useProxy <- cursor.get[Option[Boolean]]("useProxy")
} yield NamedSpecification(name, path, useProxy)
}
}
Expand Up @@ -29,7 +29,10 @@ object Github extends LazyLogging {
logger.debug(s"Matching repository: $name")
val uri = Uri(url)
val uriWithToken = uri.withAuthority(uri.authority.copy(userinfo = githubClient.settings.apiToken))
Repository(uriWithToken.toString, branch, repository.specificationPaths.map(UnnamedSpecification(_)))
Repository(uriWithToken.toString,
branch,
repository.specificationPaths.map(UnnamedSpecification(_)),
useProxy = repository.useProxy)
}

}.collect { case Some(repository) => repository }
Expand Down
Expand Up @@ -6,14 +6,17 @@ import scala.jdk.CollectionConverters._

import com.typesafe.config.{Config, ConfigFactory}

final case class RepositorySettings(location: Location, branch: Option[String] = None, specificationPaths: List[String] = Nil)
final case class RepositorySettings(location: Location,
branch: Option[String] = None,
specificationPaths: List[String] = Nil,
useProxy: Boolean)

object RepositorySettings {

def getListOfRepositories(config: Config, path: String): List[RepositorySettings] =
if (config.hasPath(path))
config.getAnyRefList(path).asScala.toList.map {
case location: String => RepositorySettings(Location.fromString(location))
case location: String => RepositorySettings(Location.fromString(location), useProxy = false)
case config: ju.Map[String, _] => fromConfig(ConfigFactory.parseMap(config))
}
else Nil
Expand All @@ -26,7 +29,8 @@ object RepositorySettings {
val specificationPaths =
if (config.hasPath("specification-paths")) config.getStringList("specification-paths").asScala.toList
else Nil
RepositorySettings(location, branch, specificationPaths)
val useProxy = config.hasPath("use-proxy") && config.getBoolean("use-proxy")
RepositorySettings(location, branch, specificationPaths, useProxy)
}
}
sealed trait Location {
Expand Down

0 comments on commit 266c8f0

Please sign in to comment.