All Downloads are FREE. Search and download functionalities are using the official Maven repository.

plugins.jobs.kubernetes.entities.scala Maven / Gradle / Ivy

package otoroshi.plugins.jobs.kubernetes

import otoroshi.env.Env
import otoroshi.models.ServiceDescriptor
import otoroshi.utils.syntax.implicits._
import play.api.Logger
import play.api.libs.json.{JsArray, JsObject, JsValue, Json}
import otoroshi.security.OtoroshiClaim
import otoroshi.ssl.{Cert, DynamicSSLEngineProvider}
import otoroshi.utils.syntax.implicits._

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

trait KubernetesEntity {
  def raw: JsValue
  def pretty: String                        = raw.prettify
  lazy val spec: JsObject                   = (raw \ "spec").asOpt[JsObject].getOrElse(Json.obj())
  lazy val creationVersion: Option[String]  = raw
    .select("metadata")
    .select("annotations")
    .select("kubectl.kubernetes.io/last-applied-configuration")
    .asOpt[String]
    .flatMap(_.parseJson.select("apiVersion").asOpt[String])
  lazy val uid: String                      = (raw \ "metadata" \ "uid").as[String]
  lazy val name: String                     = (raw \ "metadata" \ "name").as[String]
  lazy val metaKind: Option[String]         = annotations.get("io.otoroshi/kind").orElse(annotations.get("otoroshi.io/kind"))
  lazy val metaId: Option[String]           = annotations.get("io.otoroshi/id").orElse(annotations.get("otoroshi.io/id"))
  lazy val namespace: String                = (raw \ "metadata" \ "namespace").as[String]
  lazy val path: String                     = s"$namespace/$name"
  lazy val labels: Map[String, String]      = (raw \ "metadata" \ "labels").asOpt[Map[String, String]].getOrElse(Map.empty)
  lazy val annotations: Map[String, String] =
    (raw \ "metadata" \ "annotations").asOpt[Map[String, String]].getOrElse(Map.empty)
}

case class KubernetesNamespace(raw: JsValue) extends KubernetesEntity
case class KubernetesService(raw: JsValue)   extends KubernetesEntity {
  lazy val clusterIP: String = (raw \ "spec" \ "clusterIP").as[String]
}
case class KubernetesConfigMap(raw: JsValue) extends KubernetesEntity {
  lazy val rawObj                      = raw.as[JsObject]
  //lazy val corefile: String      = (raw \ "data" \ "Corefile").as[String]
  def corefile(azure: Boolean): String =
    (if (azure) (raw \ "data" \ "otoroshi.server").asOpt[String] else (raw \ "data" \ "Corefile").asOpt[String])
      .getOrElse("")
  lazy val data: JsObject              = (raw \ "data").asOpt[JsObject].getOrElse(Json.obj())
  lazy val stubDomains: JsObject       =
    (data \ "stubDomains").asOpt[String].flatMap(str => Json.parse(str).asOpt[JsObject]).getOrElse(Json.obj())
  def hasOtoroshiMesh(conf: KubernetesConfig): Boolean = {
    val dnsConfigFile  = if (conf.coreDnsAzure) "otoroshi.server" else "Corefile"
    val coreDnsNameEnv = conf.coreDnsEnv.map(e => s"$e-").getOrElse("")
    (raw \ "data" \ dnsConfigFile).asOpt[String] match {
      case None    => true // because Corefile should be there, so avoid to do something wrong
      case Some(coreFile)
          if coreFile.contains(s"### otoroshi-${coreDnsNameEnv}mesh-begin ###") && coreFile.contains(
            s"### otoroshi-${coreDnsNameEnv}mesh-end ###"
          ) =>
        true
      case Some(_) => false
    }
  }
}

case class KubernetesValidatingWebhookConfiguration(raw: JsValue) extends KubernetesEntity {
  lazy val webhooks: JsArray = (raw \ "webhooks").as[JsArray]
}

case class KubernetesMutatingWebhookConfiguration(raw: JsValue) extends KubernetesEntity {
  lazy val webhooks: JsArray = (raw \ "webhooks").as[JsArray]
}

case class KubernetesOpenshiftDnsOperatorServer(raw: JsValue) {
  lazy val name: String                        = raw.select("name").as[String]
  lazy val zones: Seq[String]                  = raw.select("zones").asOpt[Seq[String]].getOrElse(Seq.empty)
  lazy val forwardPluginUpstreams: Seq[String] =
    raw.select("forwardPlugin").select("upstreams").asOpt[Seq[String]].getOrElse(Seq.empty)
}

case class KubernetesOpenshiftDnsOperator(raw: JsValue) extends KubernetesEntity {
  lazy val servers = (raw \ "spec" \ "servers")
    .asOpt[JsArray]
    .map(_.value.map(KubernetesOpenshiftDnsOperatorServer.apply))
    .getOrElse(Seq.empty)
}

case class KubernetesEndpoint(raw: JsValue) extends KubernetesEntity

case class KubernetesOtoroshiResource(raw: JsValue) extends KubernetesEntity

case class KubernetesIngressClassParameters(raw: JsValue) {
  lazy val apiGroup: String = (raw \ "apiGroup").as[String]
  lazy val kind: String     = (raw \ "kind").as[String]
  lazy val name: String     = (raw \ "name").as[String]
}

case class KubernetesIngressClass(raw: JsValue) extends KubernetesEntity {
  lazy val controller: String                           = (spec \ "controller").as[String]
  lazy val parameters: KubernetesIngressClassParameters = KubernetesIngressClassParameters(
    (spec \ "parameters").as[JsValue]
  )
  lazy val isDefault: Boolean                           =
    annotations.get("ingressclass.kubernetes.io/is-default-class").map(_ == "true").getOrElse(false)
}

case class KubernetesIngress(raw: JsValue) extends KubernetesEntity {
  lazy val ingressClazz: Option[String]     =
    annotations.get("kubernetes.io/ingress.class").orElse(spec.select("ingressClassName").asOpt[String])
  lazy val ingressClassName: Option[String] = spec.select("ingressClassName").asOpt[String]
  lazy val ingress: IngressSupport.NetworkingV1beta1IngressItem = {
    IngressSupport.NetworkingV1beta1IngressItem.reader.reads(raw).get
  }
  def isValid(): Boolean                    = true
  def updateIngressStatus(client: KubernetesClient): Future[Unit] = {
    if (
      client.config.ingressEndpointPublishedService.isEmpty || (client.config.ingressEndpointHostname.isEmpty || client.config.ingressEndpointIp.isEmpty)
    ) {
      ().future
    } else {
      client.config.ingressEndpointPublishedService match {
        case None             => {
          // TODO: update with ingressEndpointHostname and ingressEndpointIp
        }
        case Some(pubService) => {
          // TODO: update with pubService
        }
      }
    }
    ().future
  }
  def asDescriptors(conf: KubernetesConfig, otoConfig: OtoAnnotationConfig, client: KubernetesClient, logger: Logger)(
      implicit
      env: Env,
      ec: ExecutionContext
  ): Future[Seq[ServiceDescriptor]] = {
    KubernetesIngressToDescriptor.asDescriptors(this)(conf, otoConfig, client, logger)(env, ec)
  }
}

case class KubernetesPod(raw: JsValue)        extends KubernetesEntity
case class KubernetesDeployment(raw: JsValue) extends KubernetesEntity

case class KubernetesCertSecret(raw: JsValue) extends KubernetesEntity {
  lazy val data: JsValue      = (raw \ "data").as[JsValue]
  lazy val isOtoCert: Boolean = metaKind.isDefined
  def cert: Option[Cert]      =
    Try {
      val crt = (data \ "tls.crt").asOpt[String]
      val key = (data \ "tls.key").asOpt[String]
      (crt, key) match {
        case (Some(crtData), keyDataOpt) =>
          Cert(
            "kubernetes - " + name,
            new String(DynamicSSLEngineProvider.base64Decode(crtData)),
            new String(DynamicSSLEngineProvider.base64Decode(keyDataOpt.getOrElse("")))
          ).some
        case _                           => None
      }
    }.toOption.flatten
}

case class KubernetesSecret(raw: JsValue) extends KubernetesEntity {
  lazy val theType: String                         = (raw \ "type").as[String]
  lazy val base64Data: String                      = (raw \ "data").as[String]
  lazy val data                                    = new String(OtoroshiClaim.decoder.decode(base64Data))
  lazy val stringData: Option[Map[String, String]] = (raw \ "stringData").asOpt[Map[String, String]]
  lazy val hasStringData: Boolean                  = stringData.isDefined
  def cert: KubernetesCertSecret                   = KubernetesCertSecret(raw)
}

case class OtoResHolder[T](raw: JsValue, typed: T) extends KubernetesEntity




© 2015 - 2025 Weber Informatics LLC | Privacy Policy