Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend pod spec to include missing fields and values #160

Merged
merged 1 commit into from
May 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions client/src/main/scala/skuber/Pod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,33 @@ object Pod {

def named(name: String) = Pod(metadata=ObjectMeta(name=name))
def apply(name: String, spec: Pod.Spec) : Pod = Pod(metadata=ObjectMeta(name=name), spec = Some(spec))

import DNSPolicy._

case class Spec(
containers: List[Container] = List(), // should have at least one member
initContainers: List[Container] = Nil,
volumes: List[Volume] = Nil,
restartPolicy: RestartPolicy.RestartPolicy = RestartPolicy.Always,
terminationGracePeriodSeconds: Option[Int] = None,
activeDeadlineSeconds: Option[Int] = None,
dnsPolicy: DNSPolicy.DNSPolicy = ClusterFirst,
dnsPolicy: DNSPolicy.DNSPolicy = DNSPolicy.ClusterFirst,
nodeSelector: Map[String, String] = Map(),
serviceAccountName: String ="",
nodeName: String = "",
hostNetwork: Boolean = false,
imagePullSecrets: List[LocalObjectReference] = List(),
affinity: Option[Affinity] = None,
tolerations: List[Toleration] = List(),
securityContext: Option[Security.Context] = None) {
securityContext: Option[Security.Context] = None,
hostname: Option[String] = None,
hostAliases: List[HostAlias] = Nil,
hostPID: Option[Boolean] = None,
hostIPC: Option[Boolean] = None,
automountServiceAccountToken: Option[Boolean] = None,
priority: Option[Int] = None,
priorityClassName: Option[String] = None,
schedulerName: Option[String] = None,
subdomain: Option[String] = None,
dnsConfig: Option[DNSConfig] = None) {

// a few convenience methods for fluently building out a pod spec
def addContainer(c: Container) = { this.copy(containers = c :: containers) }
Expand Down Expand Up @@ -138,6 +147,10 @@ object Pod {
case class WeightedPodAffinityTerm(weight: Int, podAffinityTerm: PodAffinityTerm)
}

case class HostAlias(ip: String, hostnames: List[String])
case class DNSConfigOption(name: String, value: String)
case class DNSConfig(nameservers: List[String] = Nil, options: List[DNSConfigOption] = Nil, searches: List[String] = Nil)

case class Status(
phase: Option[Phase.Phase] = None,
conditions: List[Condition] = Nil,
Expand All @@ -146,7 +159,10 @@ object Pod {
hostIP: Option[String] = None,
podIP: Option[String] = None,
startTime: Option[Timestamp] = None,
containerStatuses: List[Container.Status] = Nil)
containerStatuses: List[Container.Status] = Nil,
initContainerStatuses: List[Container.Status] = Nil,
qosClass: Option[String] = None,
nominatedNodeName: Option[String] = None)

case class Condition(
_type : String="Ready",
Expand Down
73 changes: 68 additions & 5 deletions client/src/main/scala/skuber/json/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ package object format {

// we make the above formatter methods available on JsPath objects via this implicit conversion
implicit def maybeEmptyFormatMethods(path: JsPath) = new MaybeEmpty(path)

// general formatting for Enumerations - derived from https://gist.github.com/mikesname/5237809
implicit def enumReads[E <: Enumeration](enum: E) : Reads[E#Value] = new Reads[E#Value] {
def reads(json: JsValue): JsResult[E#Value] = json match {
Expand Down Expand Up @@ -673,7 +673,10 @@ package object format {
(JsPath \ "hostIP").formatNullable[String] and
(JsPath \ "podIP").formatNullable[String] and
(JsPath \ "startTime").formatNullable[Timestamp] and
(JsPath \ "containerStatuses").formatMaybeEmptyList[Container.Status]
(JsPath \ "containerStatuses").formatMaybeEmptyList[Container.Status] and
(JsPath \ "initContainerStatuses").formatMaybeEmptyList[Container.Status] and
(JsPath \ "qosClass").formatNullable[String] and
(JsPath \ "nominatedNodeName").formatNullable[String]
)(Pod.Status.apply _, unlift(Pod.Status.unapply))

implicit lazy val podFormat : Format[Pod] = (
Expand Down Expand Up @@ -728,8 +731,20 @@ package object format {


implicit lazy val affinityFormat : Format[Pod.Affinity] = Json.format[Pod.Affinity]

implicit val podSpecFormat: Format[Pod.Spec] = (

implicit val hostAliasFmt: Format[Pod.HostAlias] = Json.format[Pod.HostAlias]
implicit val podDNSConfigOptionFmt: Format[Pod.DNSConfigOption] = Json.format[Pod.DNSConfigOption]
implicit val podDNSConfigFmt: Format[Pod.DNSConfig] = (
(JsPath \ "nameservers").formatMaybeEmptyList[String] and
(JsPath \ "options").formatMaybeEmptyList[Pod.DNSConfigOption] and
(JsPath \ "searches").formatMaybeEmptyList[String]
)(Pod.DNSConfig.apply _, unlift(Pod.DNSConfig.unapply))

// the following ugliness is to do with the Kubernetes pod spec schema expanding until it takes over the entire universe,
// which has finally necessitated a hack to get around Play Json limitations supporting case classes with > 22 members
// (see e.g. https://stackoverflow.com/questions/28167971/scala-case-having-22-fields-but-having-issue-with-play-json-in-scala-2-11-5)

val podSpecPartOneFormat: OFormat[(List[Container], List[Container], List[Volume], skuber.RestartPolicy.Value, Option[Int], Option[Int], skuber.DNSPolicy.Value, Map[String, String], String, String, Boolean, List[LocalObjectReference], Option[Pod.Affinity], List[Pod.Toleration], Option[Security.Context])] = (
(JsPath \ "containers").format[List[Container]] and
(JsPath \ "initContainers").formatMaybeEmptyList[Container] and
(JsPath \ "volumes").formatMaybeEmptyList[Volume] and
Expand All @@ -745,7 +760,55 @@ package object format {
(JsPath \ "affinity").formatNullable[Pod.Affinity] and
(JsPath \ "tolerations").formatMaybeEmptyList[Pod.Toleration] and
(JsPath \ "securityContext").formatNullable[Security.Context]
)(Pod.Spec.apply _, unlift(Pod.Spec.unapply))
).tupled

val podSpecPartTwoFormat: OFormat[(Option[String], List[Pod.HostAlias], Option[Boolean], Option[Boolean], Option[Boolean], Option[Int], Option[String], Option[String], Option[String], Option[Pod.DNSConfig])] = (
(JsPath \ "hostname").formatNullable[String] and
(JsPath \ "hostAliases").formatMaybeEmptyList[Pod.HostAlias] and
(JsPath \ "hostPID").formatNullable[Boolean] and
(JsPath \ "hostIPC").formatNullable[Boolean] and
(JsPath \ "automountServiceAccountToken").formatNullable[Boolean] and
(JsPath \ "priority").formatNullable[Int] and
(JsPath \ "priorityClassName").formatNullable[String] and
(JsPath \ "schedulerName").formatNullable[String] and
(JsPath \ "subdomain").formatNullable[String] and
(JsPath \ "dnsConfig").formatNullable[Pod.DNSConfig]
).tupled

implicit val podSpecFmt: Format[Pod.Spec] = (
podSpecPartOneFormat and podSpecPartTwoFormat
).apply({
case ((conts, initConts, vols, rpol, tgps, adls, dnspol, nodesel, svcac, node, hnet, ips, aff, tol, sc), (host, aliases, pid, ipc, asat, prio, prioc, sched, subd, dnsc)) =>
Pod.Spec(conts, initConts, vols, rpol, tgps, adls, dnspol, nodesel, svcac, node, hnet, ips, aff, tol, sc, host, aliases, pid, ipc, asat, prio, prioc, sched, subd, dnsc)
}, s =>(
( s.containers,
s.initContainers,
s.volumes,
s.restartPolicy,
s.terminationGracePeriodSeconds,
s.activeDeadlineSeconds,
s.dnsPolicy,
s.nodeSelector,
s.serviceAccountName,
s.nodeName,
s.hostNetwork,
s.imagePullSecrets,
s.affinity,
s.tolerations,
s.securityContext
),
( s.hostname,
s.hostAliases,
s.hostPID,
s.hostIPC,
s.automountServiceAccountToken,
s.priority,
s.priorityClassName,
s.schedulerName,
s.subdomain,
s.dnsConfig
))
)

implicit val podTemplSpecFormat: Format[Pod.Template.Spec] = Json.format[Pod.Template.Spec]
implicit lazy val podTemplFormat : Format[Pod.Template] = (
Expand Down
2 changes: 1 addition & 1 deletion client/src/main/scala/skuber/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ package object skuber {
}
object DNSPolicy extends Enumeration {
type DNSPolicy = Value
val Default,ClusterFirst = Value
val Default,ClusterFirst,ClusterFirstWithHostNet,None = Value
}
object RestartPolicy extends Enumeration {
type RestartPolicy = Value
Expand Down
4 changes: 4 additions & 0 deletions client/src/test/resources/examplePodExtendedSpec.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
"serviceAccountName": "my-account",
"terminationGracePeriodSeconds": 60,
"hostNetwork": true,
"dnsPolicy": "None",
"imagePullSecrets": [
{
"name": "secret"
}
],
"priority": 2,
"hostname": "abc",
"subdomain": "def",
"containers": [
{
"name": "basic",
Expand Down
7 changes: 6 additions & 1 deletion client/src/test/scala/skuber/json/PodFormatSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,12 @@ import Pod._
mount.readOnly mustEqual true
mount.subPath mustEqual "subpath"

pod.spec.get.securityContext.get.fsGroup == Some(2000)
pod.spec.get.securityContext.get.fsGroup mustEqual Some(2000)
pod.spec.get.priority mustEqual Some(2)
pod.spec.get.hostname mustEqual Some("abc")
pod.spec.get.subdomain mustEqual Some("def")
pod.spec.get.dnsPolicy mustEqual DNSPolicy.None
pod.spec.get.hostNetwork mustEqual true

// write and read it back in again and compare
val json = Json.toJson(pod)
Expand Down