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

io.buoyant.k8s.Api.scala Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
package io.buoyant.k8s

import com.fasterxml.jackson.annotation.JsonIgnore
import com.twitter.finagle.http
import com.twitter.finagle.http.MediaType
import com.twitter.io.{Buf, Reader}
import com.twitter.util._

/**
 * Contains various classes and methods useful for interacting with the Kubernetes API.
 */
object Api {
  val BufSize = 8 * 1024

  object Closed extends Throwable
  case class UnexpectedResponse(rsp: http.Response) extends Throwable

  /**
   * Represents an HTTP 409 Conflict response, returned by the k8s API when attempting to update a
   * resource with an out-of-date resource version or when attempting to create a resource at an
   * existing name.
   */
  case class Conflict(rsp: http.Response) extends Throwable

  /**
   * Represents an HTTP 404 Not Found response.
   */
  case class NotFound(rsp: http.Response) extends Throwable

  private[k8s] def mkreq(
    method: http.Method,
    path: String,
    content: Option[Buf],
    optParams: (String, Option[String])*
  ): http.Request = {
    val params = optParams collect { case (k, Some(v)) => (k, v) }
    val req = http.Request(path, params: _*)
    req.method = method
    req.contentType = MediaType.Json
    content.foreach(req.content = _)
    req
  }

  private[k8s] def dechunk(reader: Reader, init: Buf = Buf.Empty): Future[Buf] =
    reader.read(BufSize).flatMap {
      case Some(chunk) =>
        dechunk(reader, init.concat(chunk))
      case None =>
        Future.value(init)
    }

  private[k8s] def getContent(msg: http.Message): Future[Buf] =
    msg.headerMap.get("transfer-encoding") match {
      case Some("chunked") => dechunk(msg.reader)
      case _ => Future.value(msg.content)
    }

  private[k8s] def parse[T: Manifest](rsp: http.Response): Future[T] =
    getContent(rsp).flatMap { content =>
      Future.const(Json.read[T](content))
    }
}

/**
 * Generally required as an implicit for list resources. Provides the kubernetes-designated
 * name for the resource, as well as a means of transforming an individual instance into a
 * type-specialized [[Watch]].
 */
trait ObjectDescriptor[O <: KubeObject, W <: Watch[O]] {
  /**
   * @return the URI path segment used for serving lists of resources of type O, e.g. "endpoints",
   *         "configmaps".
   */
  def listName: String

  /**
   *
   * @param o a current object instance
   * @return a Watch (usually Watch.Modified) wrapping `o`
   */
  def toWatch(o: O): W
}

/**
 * Describes an Object in the Kubernetes API (i.e.
 * http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_endpoints)
 */
trait KubeObject {
  def apiVersion: Option[String]
  def metadata: Option[ObjectMeta]
  def kind: Option[String]
}

/**
 * Describes a List of Objects in the Kubernetes API (i.e.
 * [[http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_endpointslist EndpointsList]])
 *
 * @tparam O the type of object contained in the list
 */
trait KubeList[O <: KubeObject] {
  def items: Seq[O]
  def apiVersion: Option[String]
  def metadata: Option[ObjectMeta]
  def kind: Option[String]
}

/**
 * See https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/devel/api-conventions.md#metadata
 * for descriptions of the meanings of the contained fields.
 */
case class ObjectMeta(
  name: Option[String] = None,
  generateName: Option[String] = None,
  namespace: Option[String] = None,
  selfLink: Option[String] = None,
  uid: Option[String] = None,
  resourceVersion: Option[String] = None,
  generation: Option[String] = None,
  creationTimestamp: Option[String] = None,
  deletionTimestamp: Option[String] = None,
  labels: Option[Map[String, String]] = None,
  annotations: Option[Map[String, String]] = None
)

case class ObjectReference(
  kind: Option[String] = None,
  namespace: Option[String] = None,
  name: Option[String] = None,
  uid: Option[String] = None,
  apiVersion: Option[String] = None,
  resourceVersion: Option[String] = None,
  fieldPath: Option[String] = None
)

/**
 * An event resulting from a "watch" on the Kubernetes API:
 * http://kubernetes.io/docs/api-reference/v1/definitions/#_json_watchevent
 *
 * Note: Dealing with this class is a little clunky because we haven't been able to get Jackson to
 * handle the combination of generics and polymorphic inheritance correctly. Thus, you'll need to
 * create actual subclasses (i.e. FooWatch extends Watch[Foo]) rather than using Watch[Foo]
 * directly, and set the correct Jackson annotations on those, to ensure correct parsing.
 */
trait Watch[O <: KubeObject] {
  def resourceVersion: Option[String]
}

object Watch {
  trait WithObject[O <: KubeObject] extends Watch[O] {
    def `object`: O
    @JsonIgnore
    def resourceVersion = `object`.metadata.flatMap(_.resourceVersion)
  }
  trait Added[O <: KubeObject] extends WithObject[O]
  trait Modified[O <: KubeObject] extends WithObject[O]
  trait Deleted[O <: KubeObject] extends WithObject[O]
  trait Error[O <: KubeObject] extends Watch[O] {
    def status: Status
    @JsonIgnore
    def resourceVersion = None
  }
}

case class Status(
  kind: Option[String] = None,
  apiVersion: Option[String] = None,
  metadata: Option[ObjectMeta] = None,
  status: Option[String] = None,
  message: Option[String] = None,
  reason: Option[String] = None,
  details: Option[StatusDetails] = None,
  code: Option[Int] = None
)

case class StatusDetails(
  name: Option[String] = None,
  kind: Option[String] = None,
  causes: Option[Seq[StatusCause]] = None,
  retryAfterSeconds: Option[Int] = None
)

case class StatusCause(
  reason: Option[String] = None,
  message: Option[String] = None,
  field: Option[String] = None
)





© 2015 - 2025 Weber Informatics LLC | Privacy Policy