Skip to content

Commit

Permalink
feat: 🎸 merge docker and kubernetes provider (#333)
Browse files Browse the repository at this point in the history
Docker and Kubernetes providers share a lot of similarities.

ref: #332
  • Loading branch information
MaethorNaur committed Jan 29, 2021
1 parent 52ebd33 commit b326289
Show file tree
Hide file tree
Showing 29 changed files with 343 additions and 327 deletions.
16 changes: 4 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ lazy val root = Project("UnisonUI", file("."))
.settings(aliases)

lazy val unisonUi = Projects.unisonUi
.dependsOn(unisonUiCore,
providerDocker,
providerKubernetes,
providerGit,
providerWebhook)
.dependsOn(unisonUiCore, providerContainer, providerGit, providerWebhook)
.settings(Dependencies.unisonUi)
.settings(DockerSettings.settings)
.settings(mainClass in Compile := Some("tech.unisonui.server.Main"))
Expand All @@ -29,7 +25,7 @@ lazy val unisonUiCore = Projects.unisonUiCore
.settings(Dependencies.unisonUiCore)

lazy val providers = (project in file("providers"))
.aggregate(providerDocker, providerKubernetes, providerGit, providerWebhook)
.aggregate(providerContainer, providerGit, providerWebhook)

lazy val providerWebhook = Projects.providerWebhook
.dependsOn(unisonUiCore)
Expand All @@ -39,13 +35,9 @@ lazy val providerGit = Projects.providerGit
.dependsOn(unisonUiCore)
.settings(Dependencies.providerGit)

lazy val providerKubernetes = Projects.providerKubernetes
lazy val providerContainer = Projects.providerContainer
.dependsOn(unisonUiCore)
.settings(Dependencies.providerKubernetes)

lazy val providerDocker = Projects.providerDocker
.dependsOn(unisonUiCore)
.settings(Dependencies.providerDocker)
.settings(Dependencies.providerContainer)

val projects: Seq[ProjectReference] = Seq(
unisonUiCore,
Expand Down
9 changes: 4 additions & 5 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,13 @@ object Dependencies {
Circe.schema,
Circe.parser)

lazy val providerDocker =
lazy val providerContainer =
libraryDependencies ++= common ++ Akka.all ++ Circe.all ++ Seq(
Akka.unixDomain)

lazy val providerKubernetes = libraryDependencies ++= common ++ Akka.all ++
Seq("io.skuber" %% "skuber" % "2.6.0")
Akka.unixDomain,
"io.skuber" %% "skuber" % "2.6.0")

lazy val providerGit = libraryDependencies ++= common ++ Akka.all ++ Circe.all

lazy val providerWebhook =
libraryDependencies ++= common ++ Akka.all ++ Circe.all
}
5 changes: 2 additions & 3 deletions project/Projects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ object Projects {
mappings in Universal ++= directory("docker/statics")
)

val providerDocker = createModule("provider-docker", "providers/docker")
val providerKubernetes =
createModule("provider-kubernetes", "providers/kubernetes")
val providerContainer =
createModule("provider-container", "providers/container")
val providerGit = createModule("provider-git", "providers/git")
val providerWebhook = createModule("provider-webhook", "providers/webhook")
}
30 changes: 30 additions & 0 deletions providers/container/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
unisonui {
providers += "tech.unisonui.providers.container.ContainerProvider"
provider.container {
kubernetes {
enabled = yes
polling-interval = "1 minute"
}

docker {
enabled = yes
host = "unix:///var/run/docker.sock"
}

labels {
service-name = "unisonui.service-name"

openapi {
port = "unisonui.specification.port"
protocol = "unisonui.specification.protocol"
specification-path = "unisonui.specification.path"
use-proxy = "unisonui.specification.use-proxy"
}

grpc {
port = "unisonui.grpc.port"
tls = "unisonui.grpc.tls"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tech.unisonui.providers.container

import akka.NotUsed
import akka.actor.typed.ActorSystem
import akka.stream.scaladsl.{Merge, Source}
import com.typesafe.config.Config
import com.typesafe.scalalogging.LazyLogging
import tech.unisonui.grpc.ReflectionClientImpl
import tech.unisonui.models.ServiceEvent
import tech.unisonui.providers.Provider
import tech.unisonui.providers.container.docker.client.impl.HttpClient
import tech.unisonui.providers.container.settings.Settings
import tech.unisonui.providers.container.sources.{
DockerSource,
KubernetesSource
}
// $COVERAGE-OFF$
class ContainerProvider extends Provider with LazyLogging {

override def start(
actorSystem: ActorSystem[_],
config: Config): Source[(String, ServiceEvent), NotUsed] = {
implicit val system: ActorSystem[_] = actorSystem
val name = classOf[ContainerProvider].getCanonicalName
val settings = Settings.from(config)
val reflectionClient = new ReflectionClientImpl()
val dockerSource = settings.docker.fold(Source.empty[ServiceEvent]) {
setting =>
val client = new HttpClient(setting.host)
new DockerSource(client, reflectionClient, settings).startStreaming
}
val kubernetesSource =
settings.kubernetes.fold(Source.empty[ServiceEvent]) { setting =>
new KubernetesSource(setting.pollingInterval,
settings.labels,
reflectionClient).listCurrentAndFutureServices
}
logger.debug("Initialising container provider")
Source
.combine(dockerSource, kubernetesSource)(Merge(_))
.map(name -> _)
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.kubernetes
package tech.unisonui.providers.container.actors

import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{ActorSystem, Behavior}
Expand All @@ -10,26 +10,30 @@ import com.typesafe.scalalogging.LazyLogging
import skuber.{Service => KubernetesService}
import tech.unisonui.grpc.ReflectionClient
import tech.unisonui.models._
import tech.unisonui.providers.container.settings._

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import scala.util.chaining._
import scala.util.control.Exception.allCatch

object ServiceActor extends LazyLogging {
object KubernetesServices extends LazyLogging {
private final case class OpenApiService(address: String,
specificationPath: String,
useProxyValue: Boolean)
private final case class ServiceFoundWithFile(
id: String,
service: String,
specificationPath: String,
address: Option[String],
useProxyValue: Boolean,
grpcServer: Option[Service.Grpc.Server])
openApi: Option[OpenApiService],
grpc: Option[Service.Grpc.Server])
private val Namespace = "namespace"

sealed trait Message

object Message {
final case class Add(namespace: String, services: List[KubernetesService])
extends Message
}

def apply(settingsLabels: Labels,
queue: SourceQueueWithComplete[ServiceEvent],
servicesByNamespaces: Map[String, List[KubernetesService]] =
Expand All @@ -41,7 +45,7 @@ object ServiceActor extends LazyLogging {
message match {
case Message.Add(namespace, newServices) =>
val filteredServices = newServices.filter(service =>
getLabels(settingsLabels, service.metadata.labels).isDefined)
settingsLabels.extractLabels(service.metadata.labels).isDefined)
servicesByNamespaces
.get(namespace)
.fold(filteredServices) { services =>
Expand Down Expand Up @@ -77,23 +81,22 @@ object ServiceActor extends LazyLogging {
.foreach {
case ServiceFoundWithFile(id,
serviceName,
specificationPath,
maybeFile,
useProxy,
openApiService,
grpcServer) =>
for {
_ <- maybeFile.fold(Future.unit) { file =>
downloadFile(
queue,
Service
.OpenApi(id,
serviceName,
file,
Map(Metadata.Provider -> "kubernetes",
Metadata.File -> specificationPath,
Namespace -> namespace),
useProxy = useProxy)
)
_ <- openApiService.fold(Future.unit) {
case OpenApiService(address, specificationPath, useProxy) =>
downloadFile(
queue,
Service
.OpenApi(id,
serviceName,
address,
Map(Metadata.Provider -> "container",
Metadata.File -> specificationPath,
Namespace -> namespace),
useProxy = useProxy)
)
}
_ <- loadSchema(queue, id, serviceName, namespace, grpcServer)
} yield ()
Expand All @@ -102,32 +105,25 @@ object ServiceActor extends LazyLogging {
private def createEndpoint(
settingsLabels: Labels,
service: KubernetesService): List[ServiceFoundWithFile] =
getLabels(settingsLabels, service.metadata.labels).map {
case Labels(protocol,
port,
specificationPath,
useProxy,
grpcPort,
grpcTls) =>
val address = Try(port.toInt).toOption.flatMap {
case 0 => None
case port => Some(port)
}.map(port =>
s"$protocol://${service.copySpec.clusterIP}:$port$specificationPath")
val useProxyValue = Try(useProxy.toBoolean).getOrElse(false)
val server = grpcPort
.flatMap(port => Try(port.toInt).toOption)
.map(port =>
Service.Grpc.Server(service.copySpec.clusterIP,
port,
Try(grpcTls.toBoolean).getOrElse(false)))
ServiceFoundWithFile(service.uid,
service.name,
specificationPath,
address,
useProxyValue,
server)
}.toList
settingsLabels
.extractLabels(service.metadata.labels)
.map { case Labels(_, maybeOpenApi, maybeGrpc) =>
val openApiService = maybeOpenApi.map {
case OpenApiLabels(port, protocol, specificationPath, useProxy) =>
val address =
s"$protocol://${service.copySpec.clusterIP}:${port.toInt}$specificationPath"
val useProxyValue =
allCatch.opt(useProxy.toBoolean).getOrElse(false)
OpenApiService(address, specificationPath, useProxyValue)
}
val grpc = maybeGrpc.map { case GrpcLabels(port, tls) =>
Service.Grpc.Server(service.copySpec.clusterIP,
port.toInt,
allCatch.opt(tls.toBoolean).getOrElse(false))
}
ServiceFoundWithFile(service.uid, service.name, openApiService, grpc)
}
.toList

private def downloadFile(queue: SourceQueueWithComplete[ServiceEvent],
openapi: Service.OpenApi)(implicit
Expand Down Expand Up @@ -159,7 +155,7 @@ object ServiceActor extends LazyLogging {
.loadSchema(server)
.flatMap { schema =>
val metadata = Map(
Metadata.Provider -> "kubernetes",
Metadata.Provider -> "container",
Metadata.File -> address,
Namespace -> namespace
)
Expand All @@ -173,18 +169,4 @@ object ServiceActor extends LazyLogging {
throwable)
}
}

private def getLabels(settingsLabels: Labels,
labels: Map[String, String]): Option[Labels] =
for {
port <- labels.get(settingsLabels.port)
specificationPath =
labels.getOrElse(settingsLabels.specificationPath,
"/specification.yaml")
protocol = labels.getOrElse(settingsLabels.protocol, "http")
useProxy = labels.getOrElse(settingsLabels.useProxy, "false")
grpcPort = settingsLabels.grpcPort.flatMap(labels.get(_))
grpcTls = labels.getOrElse(settingsLabels.grpcTls, "false")
} yield
Labels(protocol, port, specificationPath, useProxy, grpcPort, grpcTls)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client
package tech.unisonui.providers.container.docker.client

import akka.NotUsed
import akka.http.scaladsl.model.{HttpResponse, Uri}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.impl
package tech.unisonui.providers.container.docker.client.impl

import akka.NotUsed
import akka.actor.typed.ActorSystem
Expand All @@ -8,8 +8,10 @@ import akka.http.scaladsl.settings.ConnectionPoolSettings
import akka.http.scaladsl.unmarshalling.Unmarshaller
import akka.stream.scaladsl.Source
import com.typesafe.scalalogging.LazyLogging
import tech.unisonui.providers.docker.client.transport.DockerSock
import tech.unisonui.providers.docker.client.{HttpClient => HttpClientInterface}
import tech.unisonui.providers.container.docker.client.transport.DockerSock
import tech.unisonui.providers.container.docker.client.{
HttpClient => HttpClientInterface
}

import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.models
package tech.unisonui.providers.container.docker.client.models

import io.circe.syntax._
import io.circe.{Decoder, Encoder, HCursor, Json}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.models
package tech.unisonui.providers.container.docker.client.models

import io.circe.{Decoder, Encoder, HCursor, Json}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.models
package tech.unisonui.providers.container.docker.client.models

import io.circe.{Decoder, Encoder, HCursor, Json}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.models
package tech.unisonui.providers.container.docker.client.models

import io.circe.syntax._
import io.circe.{Decoder, Encoder, HCursor, Json}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.models
package tech.unisonui.providers.container.docker.client.models

sealed trait State

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tech.unisonui.providers.docker.client.transport
package tech.unisonui.providers.container.docker.client.transport

import java.net.InetSocketAddress
import java.nio.file.Paths
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tech.unisonui.providers.container.settings

final case class DockerSettings(host: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package tech.unisonui.providers.container.settings

import scala.concurrent.duration.FiniteDuration
final case class KubernetesSettings(pollingInterval: FiniteDuration)

0 comments on commit b326289

Please sign in to comment.