From 8114320769a661cd9a0bc286df90b965791c8fbf Mon Sep 17 00:00:00 2001 From: David O'Riordan Date: Mon, 10 Nov 2025 08:07:08 +0000 Subject: [PATCH] Update various dependencies and remove no longer needed beta CRD test --- build.sbt | 12 +- .../scala/skuber/CustomResourceBetaSpec.scala | 225 ------------------ .../scala/skuber/api/WatchSourceSpec.scala | 2 +- project/plugins.sbt | 8 +- 4 files changed, 11 insertions(+), 236 deletions(-) delete mode 100644 client/src/it/scala/skuber/CustomResourceBetaSpec.scala diff --git a/build.sbt b/build.sbt index 08c2afa5..5b1fed33 100644 --- a/build.sbt +++ b/build.sbt @@ -3,11 +3,11 @@ resolvers += "Typesafe Releases" at "https://repo.typesafe.com/typesafe/releases val akkaVersion = "2.6.19" -val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.15.4" -val specs2 = "org.specs2" %% "specs2-core" % "4.17.0" -val scalaTest = "org.scalatest" %% "scalatest" % "3.2.14" -val mockito = "org.mockito" % "mockito-core" % "4.6.1" -val scalaTestMockito = "org.scalatestplus" %% "mockito-4-6" % "3.2.14.0" +val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.19.0" +val specs2 = "org.specs2" %% "specs2-core" % "4.23.0" +val scalaTest = "org.scalatest" %% "scalatest" % "3.2.18" +val mockito = "org.mockito" % "mockito-core" % "4.11.0" +val scalaTestMockito = "org.scalatestplus" %% "mockito-4-11" % "3.2.18.0" val akkaStreamTestKit = "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion val snakeYaml = "org.yaml" % "snakeyaml" % "2.5" @@ -21,7 +21,7 @@ val akka = "com.typesafe.akka" %% "akka-actor" % akkaVersion // Skuber uses akka logging, so the examples config uses the akka slf4j logger with logback backend val akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion -val logback = "ch.qos.logback" % "logback-classic" % "1.4.4" % Runtime +val logback = "ch.qos.logback" % "logback-classic" % "1.5.20" % Runtime // the Json formatters are based on Play Json val playJson = "com.typesafe.play" %% "play-json" % "2.9.3" diff --git a/client/src/it/scala/skuber/CustomResourceBetaSpec.scala b/client/src/it/scala/skuber/CustomResourceBetaSpec.scala deleted file mode 100644 index e825eba4..00000000 --- a/client/src/it/scala/skuber/CustomResourceBetaSpec.scala +++ /dev/null @@ -1,225 +0,0 @@ -package skuber - -import akka.stream._ -import akka.stream.scaladsl._ -import org.scalatest.concurrent.Eventually -import org.scalatest.matchers.should.Matchers -import play.api.libs.json._ -import skuber.ResourceSpecification.{ScaleSubresource, Schema, StatusSubresource, Subresources} -import skuber.apiextensions.v1beta1.CustomResourceDefinition - -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} -import scala.util.{Failure, Success} - -/** - * This is a variant of the CustomResourceSpec test that tests skuber support for the the pre-GA (v1beta1) CRD functionality - * in Kubernetes. - * - * Note that the beta CRD version is no longer supported by Kubernetes since v1.22, so users should migrate - * to using the full v1 version (as used by CustomReosurceSpec) as soon as possible, as this beta API in skuber - * will also be removed soon. - * - * @author David O'Riordan - */ -class CustomResourceBetaSpec extends K8SFixture with Eventually with Matchers { - - val testResourceName: String = java.util.UUID.randomUUID().toString - - // Convenient aliases for the custom object and list resource types to be passed to the skuber API methods - type TestResource=CustomResource[TestResource.Spec,TestResource.Status] - type TestResourceList=ListResource[TestResource] - - object TestResource { - - /* - * Define the CustomResource model (spec and status), the implicit values that need to be passed to the - * skuber API to enable it to send and receive TestResource/TestResourceList types, and the matching CRD - */ - - // TestResource model - case class Spec(desiredReplicas: Int) - - case class Status(actualReplicas: Int) - - // skuber requires these implicit json formatters to marshal and unmarshal the TestResource spec and status fields. - // The CustomResource json formatter will marshal/unmarshal these to/from "spec" and "status" subobjects - // of the overall json representation of the resource. - implicit val specFmt: Format[Spec] = Json.format[Spec] - implicit val statusFmt: Format[Status] = Json.format[Status] - - // Resource definition: defines the details of the API for the resource type on Kubernetes - // Must mirror the corresponding details in the associated CRD - see Kubernetes CRD documentation. - // This needs to be passed implicitly to the skuber API to enable it to process TestResource requests. - // The json paths in the Scale subresource must map to the replica fields in Spec and Status - // respectively above - implicit val testResourceDefinition = ResourceDefinition[TestResource]( - group = "test.skuber.io", - version = "v1alpha1", - kind = "SkuberTest", - shortNames = List("test","tests"), // not needed but handy if debugging the tests - subresources = Some(Subresources() - .withStatusSubresource // enable status subresource - .withScaleSubresource(ScaleSubresource(".spec.desiredReplicas", ".status.actualReplicas")) // enable scale subresource - ) - ) - - // the following implicit values enable the scale and status methods on the skuber API to be called for this type - // (these calls will be rejected unless the subresources are enabled on the CRD) - implicit val statusSubEnabled=CustomResource.statusMethodsEnabler[TestResource] - implicit val scaleSubEnabled=CustomResource.scalingMethodsEnabler[TestResource] - - // Construct an exportable Kubernetes CRD that mirrors the details in the matching implicit resource definition above - - // the test will create it on Kubernetes so that the subsequent test requests can be handled by the cluster - val crd=CustomResourceDefinition[TestResource] - - // Convenience method for constructing custom resources of the required type from a name snd a spec - def apply(name: String, spec: Spec) = CustomResource[Spec,Status](spec).withName(name) - } - - val initialDesiredReplicas=1 - - val modifiedDesiredReplicas=2 - val modifiedActualReplicas=3 - - behavior of "CustomResource" - - it should "create a crd" in { k8s => - k8s.create(TestResource.crd) map { c => - assert(c.name == TestResource.crd.name) - assert(c.spec.defaultVersion == "v1alpha1") - assert(c.spec.group == Some(("test.skuber.io"))) - } - } - - it should "get the newly created crd" in { k8s => - k8s.get[CustomResourceDefinition](TestResource.crd.name) map { c => - assert(c.name == TestResource.crd.name) - } - } - - it should "create a new custom resource defined by the crd" in { k8s => - val testSpec=TestResource.Spec(1) - val testResource=TestResource(testResourceName, testSpec) - k8s.create(testResource).map { testResource => - assert(testResource.name==testResourceName) - assert(testResource.spec==testSpec) - } - } - - it should "get the custom resource" in { k8s => - k8s.get[TestResource](testResourceName).map { c => - assert(c.name == testResourceName) - assert(c.spec.desiredReplicas == 1) - assert(c.status == None) - } - } - - it should "scale the desired replicas on the spec of the custom resource" in { k8s => - val updatedFut = for { - currentScale <- k8s.getScale[TestResource](testResourceName) - scaled <- k8s.updateScale[TestResource](testResourceName, currentScale.withSpecReplicas(modifiedDesiredReplicas)) - updated <- k8s.get[TestResource](testResourceName) - } yield updated - updatedFut.map { updated => - assert(updated.spec.desiredReplicas==modifiedDesiredReplicas) - } - } - - it should "update the status on the custom resource with a modified actual replicas count" in { k8s => - val status=TestResource.Status(modifiedActualReplicas) - val updatedFut = for { - testResource <- k8s.get[TestResource](testResourceName) - updatedTestResource <- k8s.updateStatus(testResource.withStatus(status)) - } yield updatedTestResource - updatedFut.map { updated => - updated.status shouldBe Some(status) - } - } - - it should "return the modified desired and actual replica counts in response to a getScale request" in { k8s => - k8s.getScale[TestResource](testResourceName).map { scale => - assert(scale.spec.replicas.contains(modifiedDesiredReplicas)) - assert(scale.status.get.replicas == modifiedActualReplicas) - } - } - - it should "delete the custom resource" in { k8s => - k8s.delete[TestResource](testResourceName) - eventually(timeout(200.seconds), interval(3.seconds)) { - val retrieveCr= k8s.get[TestResource](testResourceName) - val crRetrieved=Await.ready(retrieveCr, 2.seconds).value.get - crRetrieved match { - case s: Success[_] => assert(false) - case Failure(ex) => ex match { - case ex: K8SException if ex.status.code.contains(404) => assert(true) - case _ => assert(false) - } - } - } - } - - it should "watch the custom resources" in { k8s => - import skuber.api.client.{EventType, WatchEvent} - - import scala.collection.mutable.ListBuffer - - val testResourceName=java.util.UUID.randomUUID().toString - val testResource = TestResource(testResourceName, TestResource.Spec(1)) - - val trackedEvents = ListBuffer.empty[WatchEvent[TestResource]] - val trackEvents: Sink[WatchEvent[TestResource],_] = Sink.foreach { event => - trackedEvents += event - } - - def getCurrentResourceVersion: Future[String] = k8s.list[TestResourceList].map { l => - l.resourceVersion - } - def watchAndTrackEvents(sinceVersion: String) = - { - k8s.watchAll[TestResource](sinceResourceVersion = Some(sinceVersion)).map { crEventSource => - crEventSource - .viaMat(KillSwitches.single)(Keep.right) - .toMat(trackEvents)(Keep.both).run() - } - } - def createTestResource= k8s.create(testResource) - def deleteTestResource= k8s.delete[TestResource](testResourceName) - - val killSwitchFut = for { - currentTestResourceVersion <- getCurrentResourceVersion - (kill, _) <- watchAndTrackEvents(currentTestResourceVersion) - testResource <- createTestResource - deleted <- deleteTestResource - } yield kill - - eventually(timeout(200.seconds), interval(3.seconds)) { - trackedEvents.size shouldBe 2 - trackedEvents(0)._type shouldBe EventType.ADDED - trackedEvents(0)._object.name shouldBe testResource.name - trackedEvents(0)._object.spec shouldBe testResource.spec - trackedEvents(1)._type shouldBe EventType.DELETED - } - - // cleanup - killSwitchFut.map { killSwitch => - killSwitch.shutdown() - assert(true) - } - } - - it should "delete the crd" in { k8s => - k8s.delete[CustomResourceDefinition](TestResource.crd.name) - eventually(timeout(200.seconds), interval(3.seconds)) { - val retrieveCrd= k8s.get[CustomResourceDefinition](TestResource.crd.name) - val crdRetrieved=Await.ready(retrieveCrd, 2.seconds).value.get - crdRetrieved match { - case s: Success[_] => assert(false) - case Failure(ex) => ex match { - case ex: K8SException if ex.status.code.contains(404) => assert(true) - case _ => assert(false) - } - } - } - } -} diff --git a/client/src/test/scala/skuber/api/WatchSourceSpec.scala b/client/src/test/scala/skuber/api/WatchSourceSpec.scala index 06cb8d1f..12c185f3 100644 --- a/client/src/test/scala/skuber/api/WatchSourceSpec.scala +++ b/client/src/test/scala/skuber/api/WatchSourceSpec.scala @@ -286,7 +286,7 @@ class WatchSourceSpec extends Specification with MockitoSugar { .expectError() error must haveClass[FramingException] - error.getMessage mustEqual "Invalid JSON encountered at position [0] of [ByteString(98, 97, 100, 32, 105, 110, 112, 117, 116)]" + error.getMessage must startWith("Invalid JSON encountered at position [0] of ") verify(client, times(2)).logConfig verify(client).buildRequest( diff --git a/project/plugins.sbt b/project/plugins.sbt index acebe5cd..0130c426 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0") -addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.0") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.13") -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1") +addSbtPlugin("com.github.sbt" % "sbt-git" % "2.1.0") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.12.2") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1")