Skip to content

Commit

Permalink
feat: allow configurable choice of annotations for rolling update rev…
Browse files Browse the repository at this point in the history
…ision
  • Loading branch information
leviramsey committed Jul 25, 2024
1 parent 256af67 commit 1837af8
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ target/

# attachments created by scripts/prepare-downloads.sh
docs/src/main/paradox/attachments

# Metals detritus
project/project
5 changes: 5 additions & 0 deletions rolling-update-kubernetes/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ akka.rollingupdate.kubernetes {
pod-name = ""
pod-name = ${?KUBERNETES_POD_NAME}

# Annotations to check to determine the revision. The first one in the list which matches
# wins. The default is suitable for "vanilla" Kubernetes Deployments, but other CI/CD systems
# may set a different annotation.
revision-annotation = [ "deployment.kubernetes.io/revision" ]

secure-api-server = true

# Configuration for the Pod Deletion Cost extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ import scala.util.control.NonFatal

import system.dispatcher

override val _revisionAnnotations = settings

private implicit val sys: ActorSystem = system
private val log = Logging(system, classOf[KubernetesApiImpl])
private val http = Http()(system)
Expand Down Expand Up @@ -93,6 +95,7 @@ import scala.util.control.NonFatal
case HttpResponse(status, _, e, _) =>
e.discardBytes()
throw new PodCostException(s"Request failed with status=$status")
// Can we make this exhaustive?
}
}

Expand Down Expand Up @@ -213,7 +216,7 @@ PUTs must contain resourceVersions. Response:
case None => Future.failed(new ReadRevisionException("No replica name found"))
}

val revision = replicaSet.map(_.metadata.annotations.`deployment.kubernetes.io/revision`)
val revision = replicaSet.map(_.metadata.annotations.revision)
revision.map(Some(_)).recoverWith {
case ex =>
if (tries >= maxTries) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import akka.annotation.InternalApi
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json.DefaultJsonProtocol
import spray.json.JsonFormat
import spray.json.JsObject
import spray.json.RootJsonFormat
import spray.json.JsString
import spray.json.JsValue

/**
* INTERNAL API
*/
@InternalApi
case class ReplicaAnnotation(`deployment.kubernetes.io/revision`: String)
case class ReplicaAnnotation(revision: String, source: String, otherAnnotations: Map[String, JsValue])

/**
* INTERNAL API
Expand Down Expand Up @@ -75,6 +78,10 @@ case class Spec(pods: immutable.Seq[PodCost])
*/
@InternalApi
trait KubernetesJsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
val _revisionAnnotations: HasRevisionAnnotations = new HasRevisionAnnotations {
def revisionAnnotations = Seq("deployment.kubernetes.io/revision")
}

// If adding more formats here, remember to also add in META-INF/native-image reflect config
implicit val metadataFormat: JsonFormat[Metadata] = jsonFormat2(Metadata.apply)
implicit val podCostFormat: JsonFormat[PodCost] = jsonFormat5(PodCost.apply)
Expand All @@ -86,7 +93,35 @@ trait KubernetesJsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val podMetadataFormat: JsonFormat[PodMetadata] = jsonFormat1(PodMetadata.apply)
implicit val podFormat: RootJsonFormat[Pod] = jsonFormat1(Pod.apply)

implicit val replicaAnnotationFormat: JsonFormat[ReplicaAnnotation] = jsonFormat1(ReplicaAnnotation.apply)
implicit val replicaAnnotationFormat: RootJsonFormat[ReplicaAnnotation] =
new RootJsonFormat[ReplicaAnnotation] {
// Not sure if we ever write this out, but if we do, this will let us write out exactly what we took in
def write(ra: ReplicaAnnotation): JsValue =
if (ra.revision.nonEmpty && ra.source.nonEmpty) {
JsObject(ra.otherAnnotations + (ra.source -> JsString(ra.revision)))
} else JsObject(ra.otherAnnotations)

def read(json: JsValue): ReplicaAnnotation = {
json match {
case JsObject(fields) =>
_revisionAnnotations.revisionAnnotations.find { annotation =>
fields.get(annotation).exists(_.isInstanceOf[JsString])
} match {
case Some(winningAnnotation) =>
ReplicaAnnotation(
fields(winningAnnotation).asInstanceOf[JsString].value,
winningAnnotation,
fields - winningAnnotation)

case None =>
ReplicaAnnotation("", "", fields)
}

case _ => spray.json.deserializationError("expected an object")
}
}
}

implicit val replicaSetMedatataFormat: JsonFormat[ReplicaSetMetadata] = jsonFormat1(ReplicaSetMetadata.apply)
implicit val podReplicaSetFormat: RootJsonFormat[ReplicaSet] = jsonFormat1(ReplicaSet.apply)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

package akka.rollingupdate.kubernetes

import scala.concurrent.duration._
import akka.util.JavaDurationConverters._
import akka.annotation.InternalApi
import com.typesafe.config.Config

import scala.concurrent.duration._
import scala.jdk.CollectionConverters.ListHasAsScala

/**
* INTERNAL API
*/
Expand Down Expand Up @@ -47,7 +49,8 @@ private[kubernetes] object KubernetesSettings {
config.getString("pod-name"),
config.getBoolean("secure-api-server"),
config.getDuration("api-service-request-timeout").asScala,
customResourceSettings
customResourceSettings,
config.getStringList("revision-annotation").asScala.toList
)
}
}
Expand All @@ -67,8 +70,17 @@ private[kubernetes] class KubernetesSettings(
val secure: Boolean,
val apiServiceRequestTimeout: FiniteDuration,
val customResourceSettings: CustomResourceSettings,
val revisionAnnotations: Seq[String],
val bodyReadTimeout: FiniteDuration = 1.second
)
) extends HasRevisionAnnotations

/**
* INTERNAL API
*/
@InternalApi
private[kubernetes] trait HasRevisionAnnotations {
def revisionAnnotations: Seq[String]
}

/**
* INTERNAL API
Expand Down

0 comments on commit 1837af8

Please sign in to comment.