WORKFLOW custom controller 1.a. watch for CR updates (push) 2. parse JSON to model (case class) 3. CRUD necessary native resources 4.* update CR status 1.b. list current CRs (pull)
USE CASE MIT Kerberos Operator: deployes KDC, Kadmin creates K8s Secrets: credentials, keytabs creates principals MIT Kerberos Operator + gets Input Result
REQUIREMENTS ➤ Parse custom resources into Scala case class(es) ➤ Make as functional & generic as possible ➤ Use Cats-Effect and IOApp to watch for resource CRUD events Scala code
CRD SPEC: YAML -> SCALA final case class Krb(realm: String, principals: List[Principal]) sealed trait Password final case class Static(value: String) extends Password case object Random extends Password sealed trait Secret final case class Keytab(name: String) extends Secret final case class KeytabAndPassword(name: String) extends Secret final case class Principal( name: String, password: Password = Random, keytab: String, secret: Secret ) ADTs
CATS EFFECT TYPE CLASS def runSync[A](f: F[A]): A = F.toIO(f).unsafeRunSync() def putActionBlocking( namespace: String, action: Either[OperatorError, WatcherAction[T, U]] ): Unit = runSync(consumer.putAction(namespace, action)) implicit F: Effect[F] /** * Convert to an IO[A]. * * The law is that toIO(liftIO(ioa)) is the same as ioa */ def toIO[A](fa: F[A]): IO[A] = Effect.toIOFromRunAsync(fa)(this)
class StringPropertyDeserializer extends StdDeserializer[StringProperty]( classOf [StringProperty]) { override def deserialize(jp: JsonParser, ctxt: DeserializationContext): StringProperty = { val node = jp.getCodec.readTree[ObjectNode](jp) StringProperty(node.toString) }} DESERIALISE TO STRING @JsonDeserialize(using = classOf[StringPropertyDeserializer]) final case class StringProperty(value: String) class AnyCustomResource extends CustomResource { private var spec: StringProperty = _ … } Wrapper for spec and status properties reading to Jackson Object AST
def parseProperty[T: JsonReader](property: StringProperty, name: String) = { val reader = implicitly [JsonReader[T]] val parsed = reader.fromString(property.value) } DESERIALISE TO [T] val cr: AnyCustomResource = ??? val spec = parseProperty[Krb](cr.getSpec, “spec") val status = parseProperty[Status](cr.getStatus, "status") Now use Scala Json library to parse to type T
PURE FUNCTIONAL CLIENT? https://github.com/joan38/kubernetes-client - based on http4s, cats-effect, circe - supports core APIs: Deployments, Pods, Services, etc. - was missing some K8s APIs, but not anymore ;-) Kubernetes Client for Scala