diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fb3ac04c..a7419120 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,7 +24,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
- scala: [2.12.13, 2.13.12, 3.2.0]
+ scala: [2.12.13, 2.13.12, 3.3.1]
java: [temurin@8, temurin@17]
runs-on: ${{ matrix.os }}
steps:
@@ -115,12 +115,12 @@ jobs:
tar xf targets.tar
rm targets.tar
- - name: Download target directories (3.2.0)
+ - name: Download target directories (3.3.1)
uses: actions/download-artifact@v3
with:
- name: target-${{ matrix.os }}-3.2.0-${{ matrix.java }}
+ name: target-${{ matrix.os }}-3.3.1-${{ matrix.java }}
- - name: Inflate target directories (3.2.0)
+ - name: Inflate target directories (3.3.1)
run: |
tar xf targets.tar
rm targets.tar
diff --git a/build.sbt b/build.sbt
index 5a3ba5e0..20ead1c0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -5,9 +5,9 @@ resolvers += "Typesafe Releases" at "https://repo.typesafe.com/typesafe/releases
val scala12Version = "2.12.13"
val scala13Version = "2.13.12"
-val scala3Version = "3.2.0"
+val scala3Version = "3.3.1"
-val currentScalaVersion = scala13Version
+val currentScalaVersion = scala3Version
ThisBuild / scalaVersion := currentScalaVersion
diff --git a/client/src/main/scala/skuber/api/Configuration.scala b/client/src/main/scala/skuber/api/Configuration.scala
index 820121f2..ddaabe8c 100644
--- a/client/src/main/scala/skuber/api/Configuration.scala
+++ b/client/src/main/scala/skuber/api/Configuration.scala
@@ -3,12 +3,12 @@ package skuber.api
import org.yaml.snakeyaml.Yaml
import skuber.Namespace
import skuber.api.client._
-import skuber.api.client.token.{FileTokenAuthRefreshable, FileTokenConfiguration}
+import skuber.api.client.token.{ExecAuthConfig, ExecAuthRefreshable, FileTokenAuthRefreshable, FileTokenConfiguration}
import skuber.config.SkuberConfig
-
import java.net.URL
import java.time.Instant
import java.time.format.DateTimeFormatter
+import java.util
import java.util.{Base64, Date}
import scala.collection.JavaConverters._
import scala.concurrent.duration.{Duration, DurationInt}
@@ -178,6 +178,32 @@ object Configuration {
}
}
+ def parseExecConfig(userConfig: YamlMap): Option[ExecAuthRefreshable] = {
+ optionalValueAt[YamlMap](userConfig, "exec").flatMap { yamlExec =>
+ val args = optionalValueAt[util.ArrayList[String]](yamlExec, "args").map(_.asScala.toList).getOrElse(List.empty)
+ val env = optionalValueAt[util.ArrayList[util.Map[String, String]]](yamlExec, "env").map(_.asScala.toList.map(_.asScala.toMap)).getOrElse(List.empty)
+ val envVariables = env.flatMap { envSingle =>
+ val nameOpt = envSingle.get("name")
+ val valueOpt = envSingle.get("value")
+ (nameOpt, valueOpt) match {
+ case (Some(name), Some(value)) => Some(name -> value)
+ case _ => None
+ }
+ }.toMap
+
+ val commandOpt: Option[String] = optionalValueAt[String](yamlExec, "command")
+
+ commandOpt.map { command =>
+ val config = ExecAuthConfig(
+ cmd = command,
+ args = args,
+ envVariables = envVariables
+ )
+ ExecAuthRefreshable(config)
+ }
+ }
+ }
+
val maybeAuth = optionalValueAt[YamlMap](userConfig, "auth-provider") match {
case Some(authProvider) => authProviderRead(authProvider)
case None =>
@@ -197,7 +223,11 @@ object Configuration {
}
}
- maybeAuth.getOrElse(NoAuth)
+ val maybeExecAuth = maybeAuth orElse {
+ parseExecConfig(userConfig)
+ }
+
+ maybeExecAuth.getOrElse(NoAuth)
}
val k8sAuthInfoMap = topLevelYamlToK8SConfigMap("user", toK8SAuthInfo)
@@ -226,7 +256,6 @@ object Configuration {
*
Follows official golang client logic
*
* @return Try[Configuration]
- *
* @see https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/
* https://github.com/kubernetes/client-go/blob/master/rest/config.go#L313
* https://github.com/kubernetes-client/java/blob/master/util/src/main/java/io/kubernetes/client/util/ClientBuilder.java#L134
diff --git a/client/src/main/scala/skuber/api/client/token/ExecAuthRefreshable.scala b/client/src/main/scala/skuber/api/client/token/ExecAuthRefreshable.scala
new file mode 100644
index 00000000..43f7ad81
--- /dev/null
+++ b/client/src/main/scala/skuber/api/client/token/ExecAuthRefreshable.scala
@@ -0,0 +1,65 @@
+package skuber.api.client.token
+
+import java.nio.charset.StandardCharsets
+import java.time.{ZoneId, ZonedDateTime}
+import java.util.TimeZone
+import org.apache.commons.io.IOUtils
+import org.joda.time.{DateTime, DateTimeZone}
+import play.api.libs.json.Json
+import skuber.api.client.AuthProviderRefreshableAuth
+import scala.collection.JavaConverters._
+
+// https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#ExecConfig
+final case class ExecAuthRefreshable(config: ExecAuthConfig) extends AuthProviderRefreshableAuth {
+ override val name = "exec"
+
+ @volatile private var cachedToken: Option[RefreshableToken] = None
+
+ override def refreshToken: RefreshableToken = {
+ val output = generateToken
+ val parsed = Json.parse(output).as[ExecCredential]
+ val refreshableToken = toRefreshableToken(parsed)
+ cachedToken = Some(refreshableToken)
+ refreshableToken
+ }
+
+ def accessToken: String = this.synchronized {
+ cachedToken match {
+ case Some(token) if isTokenExpired(token) =>
+ refreshToken.accessToken
+ case None =>
+ refreshToken.accessToken
+ case Some(token) =>
+ token.accessToken
+ }
+ }
+
+ override def toString =
+ """ExecAuthRefreshable(accessToken=)""".stripMargin
+
+ override def isTokenExpired(refreshableToken: RefreshableToken): Boolean = {
+ DateTime.now(DateTimeZone.UTC).isAfter(refreshableToken.expiry)
+ }
+
+ override def generateToken: String = config.execute()
+
+ private def toRefreshableToken(execCredential: ExecCredential): RefreshableToken = {
+ val utc = ZoneId.of("UTC")
+ val now = ZonedDateTime.now(utc)
+ val expiration = execCredential.status.expirationTimestamp.getOrElse(now.plusYears(1))
+ val expirationDateTime = new DateTime(expiration.toInstant.toEpochMilli, DateTimeZone.forTimeZone(TimeZone.getTimeZone(utc)))
+
+ RefreshableToken(execCredential.status.token, expirationDateTime)
+ }
+}
+
+final case class ExecAuthConfig(cmd: String,
+ args: List[String],
+ envVariables: Map[String, String]) {
+ def execute(): String = {
+ val process = new java.lang.ProcessBuilder((Seq(cmd) ++ args).toList.asJava)
+ envVariables.map { case (name, value) => process.environment().put(name, value)}
+ val output = IOUtils.toString(process.start.getInputStream, StandardCharsets.UTF_8)
+ output
+ }
+}
diff --git a/client/src/main/scala/skuber/api/client/token/ExecCredential.scala b/client/src/main/scala/skuber/api/client/token/ExecCredential.scala
new file mode 100644
index 00000000..3746e947
--- /dev/null
+++ b/client/src/main/scala/skuber/api/client/token/ExecCredential.scala
@@ -0,0 +1,9 @@
+package skuber.api.client.token
+
+import play.api.libs.json.{Json, OFormat}
+
+final case class ExecCredential(status: ExecCredentialStatus)
+
+object ExecCredential {
+ implicit val execCredentialFmt: OFormat[ExecCredential] = Json.format[ExecCredential]
+}
diff --git a/client/src/main/scala/skuber/api/client/token/ExecCredentialStatus.scala b/client/src/main/scala/skuber/api/client/token/ExecCredentialStatus.scala
new file mode 100644
index 00000000..422360a8
--- /dev/null
+++ b/client/src/main/scala/skuber/api/client/token/ExecCredentialStatus.scala
@@ -0,0 +1,10 @@
+package skuber.api.client.token
+
+import play.api.libs.json.{Json, OFormat}
+import skuber.Timestamp
+
+final case class ExecCredentialStatus(expirationTimestamp: Option[Timestamp], token: String)
+
+object ExecCredentialStatus {
+ implicit val execCredentialStatusFmt: OFormat[ExecCredentialStatus] = Json.format[ExecCredentialStatus]
+}
\ No newline at end of file
diff --git a/client/src/test/scala/skuber/api/ConfigurationSpec.scala b/client/src/test/scala/skuber/api/ConfigurationSpec.scala
index 9e9dc499..736e44fb 100644
--- a/client/src/test/scala/skuber/api/ConfigurationSpec.scala
+++ b/client/src/test/scala/skuber/api/ConfigurationSpec.scala
@@ -1,243 +1,110 @@
package skuber.api
-import skuber._
-import org.specs2.mutable.Specification
import java.nio.file.Paths
-import java.time.format.DateTimeFormatter
-import java.time.{Instant, ZoneId}
-
+import java.time.Instant
import org.apache.pekko.actor.ActorSystem
-import com.typesafe.config.ConfigFactory
-
-import scala.util.Try
+import org.scalatest.funspec.AnyFunSpec
+import org.scalatest.matchers.should.Matchers
+import skuber._
import skuber.api.client._
+import skuber.api.client.token.ExecAuthRefreshable
+import scala.util.{Success, Try}
/**
- * @author David O'Riordan
- */
-class ConfigurationSpec extends Specification {
- val kubeConfigStr = """
-apiVersion: v1
-clusters:
-- cluster:
- api-version: v1
- server: http://cow.org:8080
- name: cow-cluster
-- cluster:
- certificate-authority: path/to/my/cafile
- server: https://horse.org:4443
- name: horse-cluster
-- cluster:
- insecure-skip-tls-verify: true
- server: https://pig.org:443
- name: pig-cluster
-contexts:
-- context:
- cluster: horse-cluster
- namespace: chisel-ns
- user: green-user
- name: federal-context
-- context:
- cluster: pig-cluster
- namespace: saw-ns
- user: blue-user
- name: queen-anne-context
-current-context: federal-context
-kind: Config
-preferences:
- colors: true
-users:
-- name: blue-user
- user:
- token: blue-token
-- name: green-user
- user:
- client-certificate: path/to/my/client/cert
- client-key: path/to/my/client/key
-- name: jwt-user
- user:
- auth-provider:
- config:
- client-id: tectonic
- client-secret: secret
- extra-scopes: groups
- id-token: jwt-token
- idp-certificate-authority-data: data
- idp-issuer-url: https://xyz/identity
- refresh-token: refresh
- name: oidc
-- name: gke-user
- user:
- auth-provider:
- config:
- access-token: myAccessToken
- cmd-args: config config-helper --format=json
- cmd-path: /home/user/google-cloud-sdk/bin/gcloud
- expiry: 2018-03-04T14:08:18Z
- expiry-key: '{.credential.token_expiry}'
- token-key: '{.credential.access_token}'
- name: gcp
-- name: string-date-gke-user
- user:
- auth-provider:
- config:
- access-token: myAccessToken
- cmd-args: config config-helper --format=json
- cmd-path: /home/user/google-cloud-sdk/bin/gcloud
- expiry: "2018-03-04T14:08:18Z"
- expiry-key: '{.credential.token_expiry}'
- token-key: '{.credential.access_token}'
- name: gcp
-- name: other-date-gke-user
- user:
- auth-provider:
- config:
- cmd-args: config config-helper --format=json
- cmd-path: /home/user/google-cloud-sdk/bin/gcloud
- expiry: "2018-03-04 14:08:18"
- expiry-key: '{.credential.token_expiry}'
- token-key: '{.credential.access_token}'
- name: gcp
-"""
-
- implicit val system: ActorSystem =ActorSystem("test")
- implicit val loggingContext: LoggingContext = new LoggingContext { override def output:String="test" }
-
- "An example kubeconfig file can be parsed correctly" >> {
- val is = new java.io.ByteArrayInputStream(kubeConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
- val k8sConfig = K8SConfiguration.parseKubeconfigStream(is)
- val parsedFromStringConfig = k8sConfig.get
-
- // construct equivalent config directly for comparison
-
- val cowCluster=K8SCluster("v1", "http://cow.org:8080",false, clusterName = Some("cow-cluster"))
- val horseCluster=K8SCluster("v1","https://horse.org:4443", false, certificateAuthority=Some(Left("path/to/my/cafile")), clusterName = Some("horse-cluster"))
- val pigCluster=K8SCluster("v1", "https://pig.org:443", true, clusterName = Some("pig-cluster"))
- val clusters=Map("cow-cluster" -> cowCluster,"horse-cluster"->horseCluster,"pig-cluster"->pigCluster)
-
- val blueUser = TokenAuth("blue-token")
- val greenUser = CertAuth(clientCertificate = Left("path/to/my/client/cert"), clientKey = Left("path/to/my/client/key"), user = None)
- val jwtUser= OidcAuth(idToken = "jwt-token")
- val gcpUser = GcpAuth(accessToken = Some("myAccessToken"), expiry = Some(Instant.parse("2018-03-04T14:08:18Z")),
- cmdPath = "/home/user/google-cloud-sdk/bin/gcloud", cmdArgs = "config config-helper --format=json")
- val noAccessTokenGcpUser = GcpAuth(accessToken = None, expiry = None,
- cmdPath = "/home/user/google-cloud-sdk/bin/gcloud", cmdArgs = "config config-helper --format=json")
- val users=Map("blue-user"->blueUser,"green-user"->greenUser,"jwt-user"->jwtUser, "gke-user"->gcpUser,
- "string-date-gke-user"->gcpUser, "other-date-gke-user" -> noAccessTokenGcpUser)
-
- val federalContext=K8SContext(horseCluster,greenUser,Namespace.forName("chisel-ns"))
- val queenAnneContext=K8SContext(pigCluster,blueUser, Namespace.forName("saw-ns"))
- val contexts=Map("federal-context"->federalContext,"queen-anne-context"->queenAnneContext)
-
- val directlyConstructedConfig=Configuration(clusters,contexts,federalContext,users)
- directlyConstructedConfig.clusters mustEqual parsedFromStringConfig.clusters
- directlyConstructedConfig.contexts mustEqual parsedFromStringConfig.contexts
- directlyConstructedConfig.users mustEqual parsedFromStringConfig.users
- directlyConstructedConfig.currentContext mustEqual parsedFromStringConfig.currentContext
-
- directlyConstructedConfig mustEqual parsedFromStringConfig
- }
+ * @author David O'Riordan
+ */
+class ConfigurationSpec extends AnyFunSpec with Matchers {
- "Parse EC private keys from kubeconfig file" >> {
- val ecConfigStr = """
-apiVersion: v1
-clusters:
-- cluster:
- certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJnRENDQVNlZ0F3SUJBZ0lVVWFxMkJNMFhaazBVb001OENGRXh2aEk0TWp3d0NnWUlLb1pJemowRUF3SXcKSFRFYk1Ca0dBMVVFQXhNU1ZVTlFJRU5zYVdWdWRDQlNiMjkwSUVOQk1CNFhEVEU0TURNeE9URTFOVEF3TUZvWApEVEl6TURNeE9ERTFOVEF3TUZvd0hURWJNQmtHQTFVRUF4TVNWVU5RSUVOc2FXVnVkQ0JTYjI5MElFTkJNRmt3CkV3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFa3pNY2JrNFRNc3lVcWcyYklKL050c2hCemxWcDcrenQKZ0trVHdHbGdYb09rZ3l3ckNBaU1YWnk4SG96dFE2NXJ3dDV1bUI1S0xXL3hSUi9vNExPclNxTkZNRU13RGdZRApWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFJd0hRWURWUjBPQkJZRUZKc2g0cTlvCkpZV09vMGsxdGJqQlpDbkM1eFdvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSURlMmpwR0ptWlNTL0tISGxmSnEKdnU5YXVzZCs5Nk5rR0g1SGFyWEN0azRtQWlCSnlUSUYyZk5aZ2xzZEc3USs0aG5TZ21EeEgzWUd0K0RjVzJiZwpiY0VlcFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCYWpDQ0FSQ2dBd0lCQWdJVVpaTTJPUFQwbTQxRGZDczFMRm5wYnNhL3hZb3dDZ1lJS29aSXpqMEVBd0l3CkV6RVJNQThHQTFVRUF4TUljM2RoY20wdFkyRXdIaGNOTVRnd016RTVNVFUxTURBd1doY05Nemd3TXpFME1UVTEKTURBd1dqQVRNUkV3RHdZRFZRUURFd2h6ZDJGeWJTMWpZVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSApBMElBQk5zVUo1YnhvRWZuNVVXS21TQ3Zoc3NlcDdubkpPa1dLUFVLaXgzSnhvbzlNNHp1WUVCdkpFV0VacmJnCmJyVWNPMHZyM3BWemxBUm83TXJZbk1MS09TbWpRakJBTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTdGhPTHVMSXNXL2pPOHcwSjJYM3hDM0FVY1FEQUtCZ2dxaGtqTwpQUVFEQWdOSUFEQkZBaUVBOTQwcGJxREJ6aGorTXNIMlhDUWRpUnJVQkFmTzVkV0YrdWFaUElnOHBHOENJSFF5ClNRQjhFS2wzcmZPVnpSOS9mU3FINm9kYVZQQk1GK3lqWk5VYnhFREgKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
- server: https://horse.org:4443
- name: horse-cluster
-contexts:
-- context:
- cluster: horse-cluster
- namespace: chisel-ns
- user: ec-user
- name: federal-context
-current-context: federal-context
-kind: Config
-preferences:
- colors: true
-users:
-- name: ec-user
- user:
- client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNCRENDQWF1Z0F3SUJBZ0lVSjBuWWwvU1UycWRmT2RUMUlGUW4rL2xhOU5vd0NnWUlLb1pJemowRUF3SXcKRXpFUk1BOEdBMVVFQXhNSWMzZGhjbTB0WTJFd0hoY05NVGd3TXpFNU1UY3hNREF3V2hjTk1qZ3dNekUyTVRjeApNREF3V2pCc01Ra3dCd1lEVlFRR0V3QXhDVEFIQmdOVkJBZ1RBREVKTUFjR0ExVUVCeE1BTVNnd0pnWURWUVFLCkV4OVBjbU5oT2lCMmIyTnROSEZpZDJadk1XdHNhalZ2Ykhwdk5UTnViR3R2TVE4d0RRWURWUVFMRXdaRGJHbGwKYm5ReERqQU1CZ05WQkFNVEJXRmtiV2x1TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQytUZAoydnJzbFI2dkpJQzlOSDNNbDZUV0dySVU5TkhsczEyakoybW1KTjZ5Q3ljdDA1eW9qaGMxN1JjWlRzVTZVQTdyCm55T1pkQmhwd0plRGlUTEk5S09CZ3pDQmdEQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUkKS3dZQkJRVUhBd0l3REFZRFZSMFRBUUgvQkFJd0FEQWRCZ05WSFE0RUZnUVVZbkd3ZVpXVjZ4Mkl2YlFEWi9IUQpvS1dpekZzd0h3WURWUjBqQkJnd0ZvQVVyWVRpN2l5TEZ2NHp2TU5DZGw5OFF0d0ZIRUF3Q3dZRFZSMFJCQVF3CkFvRUFNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJQmlSMnA5RUJRUDc1TEVsTUtXcEplQTc3aFZSTzA1V2VZN3QKQ3BjM0cwMEJBaUI0Um5odjJvZFUxWXB1Y25aMjNmWGFHTXN2aS9BaVhyekViOFE4M2lSeFNnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
- client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUpPVDFJK3p0SVc4bEhKYVVkZFBnSEljTU1YK21abjMzNnRtOXdFRVhGelNvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFQytUZDJ2cnNsUjZ2SklDOU5IM01sNlRXR3JJVTlOSGxzMTJqSjJtbUpONnlDeWN0MDV5bwpqaGMxN1JjWlRzVTZVQTdybnlPWmRCaHB3SmVEaVRMSTlBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
-"""
- val is = new java.io.ByteArrayInputStream(ecConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
- val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
- Try(k8sInit(k8sConfig)) must beSuccessfulTry
- }
- "Parse RSA private keys from kubeconfig file" >> {
- val ecConfigStr = """
-apiVersion: v1
-clusters:
-- cluster:
- certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJnRENDQVNlZ0F3SUJBZ0lVVWFxMkJNMFhaazBVb001OENGRXh2aEk0TWp3d0NnWUlLb1pJemowRUF3SXcKSFRFYk1Ca0dBMVVFQXhNU1ZVTlFJRU5zYVdWdWRDQlNiMjkwSUVOQk1CNFhEVEU0TURNeE9URTFOVEF3TUZvWApEVEl6TURNeE9ERTFOVEF3TUZvd0hURWJNQmtHQTFVRUF4TVNWVU5RSUVOc2FXVnVkQ0JTYjI5MElFTkJNRmt3CkV3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFa3pNY2JrNFRNc3lVcWcyYklKL050c2hCemxWcDcrenQKZ0trVHdHbGdYb09rZ3l3ckNBaU1YWnk4SG96dFE2NXJ3dDV1bUI1S0xXL3hSUi9vNExPclNxTkZNRU13RGdZRApWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFJd0hRWURWUjBPQkJZRUZKc2g0cTlvCkpZV09vMGsxdGJqQlpDbkM1eFdvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSURlMmpwR0ptWlNTL0tISGxmSnEKdnU5YXVzZCs5Nk5rR0g1SGFyWEN0azRtQWlCSnlUSUYyZk5aZ2xzZEc3USs0aG5TZ21EeEgzWUd0K0RjVzJiZwpiY0VlcFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCYWpDQ0FSQ2dBd0lCQWdJVVpaTTJPUFQwbTQxRGZDczFMRm5wYnNhL3hZb3dDZ1lJS29aSXpqMEVBd0l3CkV6RVJNQThHQTFVRUF4TUljM2RoY20wdFkyRXdIaGNOTVRnd016RTVNVFUxTURBd1doY05Nemd3TXpFME1UVTEKTURBd1dqQVRNUkV3RHdZRFZRUURFd2h6ZDJGeWJTMWpZVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSApBMElBQk5zVUo1YnhvRWZuNVVXS21TQ3Zoc3NlcDdubkpPa1dLUFVLaXgzSnhvbzlNNHp1WUVCdkpFV0VacmJnCmJyVWNPMHZyM3BWemxBUm83TXJZbk1MS09TbWpRakJBTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTdGhPTHVMSXNXL2pPOHcwSjJYM3hDM0FVY1FEQUtCZ2dxaGtqTwpQUVFEQWdOSUFEQkZBaUVBOTQwcGJxREJ6aGorTXNIMlhDUWRpUnJVQkFmTzVkV0YrdWFaUElnOHBHOENJSFF5ClNRQjhFS2wzcmZPVnpSOS9mU3FINm9kYVZQQk1GK3lqWk5VYnhFREgKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
- server: https://horse.org:4443
- name: horse-cluster
-contexts:
-- context:
- cluster: horse-cluster
- namespace: chisel-ns
- user: rsa-user
- name: federal-context
-current-context: federal-context
-kind: Config
-preferences:
- colors: true
-users:
-- name: rsa-user
- user:
- client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNTRENDQVRDZ0F3SUJBZ0lJRlJwWFNVQ0VkSTh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBek1Ea3hPVEk1TlRaYUZ3MHhPVEF6TURreE9USTVOVFphTURNeApGREFTQmdOVkJBb1RDMFJ2WTJ0bGNpQkpibU11TVJzd0dRWURWUVFERXhKa2IyTnJaWEl0Wm05eUxXUmxjMnQwCmIzQXdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBTkZFRnRKT3VLS045VmtRKzJ5V0Z6d08KQUJPZ2hRM3lpSExBUkpQOHBxWHRDQ3VUV05weHdiUnM5TjlQcnhTbjBCblZzeXlreGlRNk12cHpLOWtDeWxBTgovWDZPbzFqWXgvK1BYdHp1NDAxc3VwbkhzSXI5S1VNQXhHVEdOK0NieXlRL3ZwTDlNSnVEV1VLUU1HYUtjNkFTCk5OdkEwVUVNWENQSTQrMHN0ZlFCQWdNQkFBR2pBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBOFdtNk4KdWk1cC9URlBURHRsczRpdm93cWlhbTR2MVM5aTVtMitSQXBCRUZralpXek0xVDhRZ2dUc1FsdDY2cGhYR0h2VwphenBKYzd3ajQzN082aURnQ0UwdXFiYmQ3bGRPNk1vb1Z6azNTaE5rU2YrUVNQd3dRdzlBQlRNR01JcC9qYzRFClk1S0Y1dG5iQTl6b3RTWUpid1JaVG1JQUVTSVQydFhKaWlyUFBLTXI3ekhTVkNpZVJWM1JmMWUwNFBCb3JnOUoKLzVoZGVzNDRUWEdiSSt3OURqaHV2ZGhRN0h2REdsdjZ6MmpsSy9hYXNxQXNoalFtVW9Hd0REelBsbGdkUm5adgp2cWd2WnovSVZNcVY5eEEzb2ZDOUwxUGF1ekFGdExjNHVZTFhFa1JsR2dFcVA2N2RjbVlxZFJWQXA4WkVBLzVqCk05aXFNdk11NnN5c3hTQWEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
- client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWEFJQkFBS0JnUURSUkJiU1RyaWlqZlZaRVB0c2xoYzhEZ0FUb0lVTjhvaHl3RVNUL0thbDdRZ3JrMWphCmNjRzBiUFRmVDY4VXA5QVoxYk1zcE1Za09qTDZjeXZaQXNwUURmMStqcU5ZMk1mL2oxN2M3dU5OYkxxWng3Q0sKL1NsREFNUmt4amZnbThza1A3NlMvVENiZzFsQ2tEQm1pbk9nRWpUYndORkJERndqeU9QdExMWDBBUUlEQVFBQgpBb0dBU1daVGh1S2J1bENHalAzNjRpUm04K2FKT2xra01qY3VpdWxMWklqS3Z3bzd3bVVGVm1GdUt1WElvZ2NtCkJ0MnhqVTQ2Y1Y4K0xIakpaclU4M1Bvd2tXOHQycVE5aFdhZkdlbVY0bWhuYjFEYWNnRlNPMjZscytFT0NzODQKTDJONHR6UnpmTVFYZHd1cG56U1RCNjRsV1hDbjN3WS9kVm0yQUg5QlN2NVY2cDBDUVFEYmZMSlh1ZWlNRU1XOAptcE5zMVJBejE2MmhmWUxHVU5idFhCSk5xcm8xUTRtUS81enlCbWovUGFnSTROWWh3amxmSWZBbUVQcW5uanVHCmlqM0lKV29MQWtFQTlCUWE4R0dURnRkK2djczVzNmtOejFpbWdMR2FrRGRWcStHZitzaktvejc1WFg3c0tLeXkKTE1uVzE4ZzlKaGhSL3d1UzJzVlFNU1EwK2l6dld1NnRvd0pCQUo0M1V5L2R1WDVPRU53VjZUUEltcmRrUDZ0cgptRHR3eHAydmd4b3RlYkV2a0JqUHljakZTaWJEd1Q4MUkrYU41V0ZvUzM2Rk9zcGRTN2QrSzI3OVdXVUNRR2RpCjNNWlZqbWh1ZnplYlRhVzhSZzArRDhrVGNkVUVtMVZqRE5DOW5KZnBaTmNsbkFMZW85bzA1THdpSlVTdHFJM1AKNlRTaHY0WVJRQjk0U1NyTFR1RUNRQXRKOVYvVUg3WTl4cTlkd3JzVkZTM2FFTlFDTitsQThWQjBjcWZOSDlpUgpORFlZRTdGblJQV244VmlNdndsT3NzNmVUTjhIWGRrbXo2Yy8vV0NycENNPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
-"""
- val is = new java.io.ByteArrayInputStream(ecConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
- val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
- Try(k8sInit(k8sConfig)) must beSuccessfulTry
+ implicit val system: ActorSystem = ActorSystem("test")
+ implicit val loggingContext: LoggingContext = new LoggingContext {
+ override def output: String = "test"
}
+ describe("Test kube config") {
+ it("An example kubeconfig file can be parsed correctly") {
+ val is = new java.io.ByteArrayInputStream(TestData.kubeConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
+ val k8sConfig = K8SConfiguration.parseKubeconfigStream(is)
+ val parsedFromStringConfig = k8sConfig.get
- "Parse PKCS#8 private keys from kubeconfig file" >> {
- val pkcs8str = """
-apiVersion: v1
-clusters:
-- cluster:
- certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJnRENDQVNlZ0F3SUJBZ0lVVWFxMkJNMFhaazBVb001OENGRXh2aEk0TWp3d0NnWUlLb1pJemowRUF3SXcKSFRFYk1Ca0dBMVVFQXhNU1ZVTlFJRU5zYVdWdWRDQlNiMjkwSUVOQk1CNFhEVEU0TURNeE9URTFOVEF3TUZvWApEVEl6TURNeE9ERTFOVEF3TUZvd0hURWJNQmtHQTFVRUF4TVNWVU5RSUVOc2FXVnVkQ0JTYjI5MElFTkJNRmt3CkV3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFa3pNY2JrNFRNc3lVcWcyYklKL050c2hCemxWcDcrenQKZ0trVHdHbGdYb09rZ3l3ckNBaU1YWnk4SG96dFE2NXJ3dDV1bUI1S0xXL3hSUi9vNExPclNxTkZNRU13RGdZRApWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFJd0hRWURWUjBPQkJZRUZKc2g0cTlvCkpZV09vMGsxdGJqQlpDbkM1eFdvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSURlMmpwR0ptWlNTL0tISGxmSnEKdnU5YXVzZCs5Nk5rR0g1SGFyWEN0azRtQWlCSnlUSUYyZk5aZ2xzZEc3USs0aG5TZ21EeEgzWUd0K0RjVzJiZwpiY0VlcFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCYWpDQ0FSQ2dBd0lCQWdJVVpaTTJPUFQwbTQxRGZDczFMRm5wYnNhL3hZb3dDZ1lJS29aSXpqMEVBd0l3CkV6RVJNQThHQTFVRUF4TUljM2RoY20wdFkyRXdIaGNOTVRnd016RTVNVFUxTURBd1doY05Nemd3TXpFME1UVTEKTURBd1dqQVRNUkV3RHdZRFZRUURFd2h6ZDJGeWJTMWpZVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSApBMElBQk5zVUo1YnhvRWZuNVVXS21TQ3Zoc3NlcDdubkpPa1dLUFVLaXgzSnhvbzlNNHp1WUVCdkpFV0VacmJnCmJyVWNPMHZyM3BWemxBUm83TXJZbk1MS09TbWpRakJBTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTdGhPTHVMSXNXL2pPOHcwSjJYM3hDM0FVY1FEQUtCZ2dxaGtqTwpQUVFEQWdOSUFEQkZBaUVBOTQwcGJxREJ6aGorTXNIMlhDUWRpUnJVQkFmTzVkV0YrdWFaUElnOHBHOENJSFF5ClNRQjhFS2wzcmZPVnpSOS9mU3FINm9kYVZQQk1GK3lqWk5VYnhFREgKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
- server: https://horse.org:4443
- name: horse-cluster
-contexts:
-- context:
- cluster: horse-cluster
- namespace: chisel-ns
- user: rsa-user
- name: federal-context
-current-context: federal-context
-kind: Config
-preferences:
- colors: true
-users:
-- name: rsa-user
- user:
- client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNTRENDQVRDZ0F3SUJBZ0lJRlJwWFNVQ0VkSTh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBek1Ea3hPVEk1TlRaYUZ3MHhPVEF6TURreE9USTVOVFphTURNeApGREFTQmdOVkJBb1RDMFJ2WTJ0bGNpQkpibU11TVJzd0dRWURWUVFERXhKa2IyTnJaWEl0Wm05eUxXUmxjMnQwCmIzQXdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBTkZFRnRKT3VLS045VmtRKzJ5V0Z6d08KQUJPZ2hRM3lpSExBUkpQOHBxWHRDQ3VUV05weHdiUnM5TjlQcnhTbjBCblZzeXlreGlRNk12cHpLOWtDeWxBTgovWDZPbzFqWXgvK1BYdHp1NDAxc3VwbkhzSXI5S1VNQXhHVEdOK0NieXlRL3ZwTDlNSnVEV1VLUU1HYUtjNkFTCk5OdkEwVUVNWENQSTQrMHN0ZlFCQWdNQkFBR2pBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBOFdtNk4KdWk1cC9URlBURHRsczRpdm93cWlhbTR2MVM5aTVtMitSQXBCRUZralpXek0xVDhRZ2dUc1FsdDY2cGhYR0h2VwphenBKYzd3ajQzN082aURnQ0UwdXFiYmQ3bGRPNk1vb1Z6azNTaE5rU2YrUVNQd3dRdzlBQlRNR01JcC9qYzRFClk1S0Y1dG5iQTl6b3RTWUpid1JaVG1JQUVTSVQydFhKaWlyUFBLTXI3ekhTVkNpZVJWM1JmMWUwNFBCb3JnOUoKLzVoZGVzNDRUWEdiSSt3OURqaHV2ZGhRN0h2REdsdjZ6MmpsSy9hYXNxQXNoalFtVW9Hd0REelBsbGdkUm5adgp2cWd2WnovSVZNcVY5eEEzb2ZDOUwxUGF1ekFGdExjNHVZTFhFa1JsR2dFcVA2N2RjbVlxZFJWQXA4WkVBLzVqCk05aXFNdk11NnN5c3hTQWEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
- client-key-data: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ2s1UFVqN08waGJ5VWNscFIKMTArQWNod3d4ZjZabWZmZnEyYjNBUVJjWE5LaFJBTkNBQVFMNU4zYSt1eVZIcThrZ0wwMGZjeVhwTllhc2hUMAowZVd6WGFNbmFhWWszcklMSnkzVG5LaU9Gelh0RnhsT3hUcFFEdXVmSTVsMEdHbkFsNE9KTXNqMAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
-"""
- val is = new java.io.ByteArrayInputStream(pkcs8str.getBytes(java.nio.charset.Charset.forName("UTF-8")))
- val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
- Try(k8sInit(k8sConfig)) must beSuccessfulTry
- }
+ // construct equivalent config directly for comparison
+
+ val cowCluster = K8SCluster("v1", "http://cow.org:8080", false, clusterName = Some("cow-cluster"))
+ val horseCluster = K8SCluster("v1", "https://horse.org:4443", false, certificateAuthority = Some(Left("path/to/my/cafile")), clusterName = Some("horse-cluster"))
+ val pigCluster = K8SCluster("v1", "https://pig.org:443", true, clusterName = Some("pig-cluster"))
+ val clusters = Map("cow-cluster" -> cowCluster, "horse-cluster" -> horseCluster, "pig-cluster" -> pigCluster)
+
+ val blueUser = TokenAuth("blue-token")
+ val greenUser = CertAuth(clientCertificate = Left("path/to/my/client/cert"), clientKey = Left("path/to/my/client/key"), user = None)
+ val jwtUser = OidcAuth(idToken = "jwt-token")
+ val gcpUser = GcpAuth(accessToken = Some("myAccessToken"), expiry = Some(Instant.parse("2018-03-04T14:08:18Z")),
+ cmdPath = "/home/user/google-cloud-sdk/bin/gcloud", cmdArgs = "config config-helper --format=json")
+ val noAccessTokenGcpUser = GcpAuth(accessToken = None, expiry = None,
+ cmdPath = "/home/user/google-cloud-sdk/bin/gcloud", cmdArgs = "config config-helper --format=json")
+ val users = Map("blue-user" -> blueUser, "green-user" -> greenUser, "jwt-user" -> jwtUser, "gke-user" -> gcpUser,
+ "string-date-gke-user" -> gcpUser, "other-date-gke-user" -> noAccessTokenGcpUser)
+
+ val federalContext = K8SContext(horseCluster, greenUser, Namespace.forName("chisel-ns"))
+ val queenAnneContext = K8SContext(pigCluster, blueUser, Namespace.forName("saw-ns"))
+ val contexts = Map("federal-context" -> federalContext, "queen-anne-context" -> queenAnneContext)
+
+ val directlyConstructedConfig = Configuration(clusters, contexts, federalContext, users)
+ directlyConstructedConfig.clusters shouldBe parsedFromStringConfig.clusters
+ directlyConstructedConfig.contexts shouldBe parsedFromStringConfig.contexts
+ directlyConstructedConfig.users shouldBe parsedFromStringConfig.users
+ directlyConstructedConfig.currentContext shouldBe parsedFromStringConfig.currentContext
+
+ directlyConstructedConfig shouldBe parsedFromStringConfig
+ }
- "If kubeconfig is not found at expected path then a Failure is returned" >> {
+ it("Parse EC private keys from kubeconfig file") {
+
+ val is = new java.io.ByteArrayInputStream(TestData.ecConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
+ val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
+ Try(k8sInit(k8sConfig)) shouldBe a[Success[KubernetesClient]]
+ }
+
+ it("Parse RSA private keys from kubeconfig file") {
+ val is = new java.io.ByteArrayInputStream(TestData.ecConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
+ val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
+ Try(k8sInit(k8sConfig)) shouldBe a[Success[KubernetesClient]]
+ }
+
+ it("Parse PKCS#8 private keys from kubeconfig file") {
+
+ val is = new java.io.ByteArrayInputStream(TestData.pkcs8str.getBytes(java.nio.charset.Charset.forName("UTF-8")))
+ val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
+ Try(k8sInit(k8sConfig)) shouldBe a[Success[KubernetesClient]]
+ }
+
+ it("If kubeconfig is not found at expected path then a Failure is returned") {
import java.nio.file.Paths
- val path=Paths.get("file:///doesNotExist")
+ val path = Paths.get("file:///doesNotExist")
val parsed = Configuration.parseKubeconfigFile(path)
- parsed.isFailure mustEqual true
- }
+ parsed.isFailure shouldBe true
+ }
- "if a relative path and directory are specified, then the parsed config must contain the fully expanded paths" >> {
- val is = new java.io.ByteArrayInputStream(kubeConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
- val k8sConfig = K8SConfiguration.parseKubeconfigStream(is, Some(Paths.get("/top/level/path")))
- val parsedFromStringConfig = k8sConfig.get
- val clientCertificate = parsedFromStringConfig.users("green-user").asInstanceOf[CertAuth].clientCertificate
- clientCertificate mustEqual Left("/top/level/path/path/to/my/client/cert")
- }
+ it("if a relative path and directory are specified, then the parsed config must contain the fully expanded paths") {
+ val is = new java.io.ByteArrayInputStream(TestData.kubeConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
+ val k8sConfig = K8SConfiguration.parseKubeconfigStream(is, Some(Paths.get("/top/level/path")))
+ val parsedFromStringConfig = k8sConfig.get
+ val clientCertificate = parsedFromStringConfig.users("green-user").asInstanceOf[CertAuth].clientCertificate
+ clientCertificate shouldBe Left("/top/level/path/path/to/my/client/cert")
+ }
+
+ it("Parse exec kubeconfig file") {
+ val is = new java.io.ByteArrayInputStream(TestData.execConfigStr.getBytes(java.nio.charset.Charset.forName("UTF-8")))
+
+ val k8sConfig = K8SConfiguration.parseKubeconfigStream(is).get
+ k8sConfig.currentContext.authInfo shouldBe a[ExecAuthRefreshable]
+ k8sConfig.currentContext.authInfo match {
+ case exec: ExecAuthRefreshable =>
+ exec.config.args shouldBe List("--region", "us-east-1", "eks", "get-token", "--cluster-name", "test-cluster", "--output", "json")
+ exec.config.cmd shouldBe "aws"
+ exec.config.envVariables shouldBe Map("AWS_PROFILE" -> "default")
+ case _ => fail("should be ExecAuthRefreshable")
+ }
+ }
-}
+
+ }
+}
\ No newline at end of file
diff --git a/client/src/test/scala/skuber/api/TestData.scala b/client/src/test/scala/skuber/api/TestData.scala
new file mode 100644
index 00000000..a91aa6d4
--- /dev/null
+++ b/client/src/test/scala/skuber/api/TestData.scala
@@ -0,0 +1,179 @@
+package skuber.api
+
+object TestData {
+
+ val kubeConfigStr =
+ """
+apiVersion: v1
+clusters:
+- cluster:
+ api-version: v1
+ server: http://cow.org:8080
+ name: cow-cluster
+- cluster:
+ certificate-authority: path/to/my/cafile
+ server: https://horse.org:4443
+ name: horse-cluster
+- cluster:
+ insecure-skip-tls-verify: true
+ server: https://pig.org:443
+ name: pig-cluster
+contexts:
+- context:
+ cluster: horse-cluster
+ namespace: chisel-ns
+ user: green-user
+ name: federal-context
+- context:
+ cluster: pig-cluster
+ namespace: saw-ns
+ user: blue-user
+ name: queen-anne-context
+current-context: federal-context
+kind: Config
+preferences:
+ colors: true
+users:
+- name: blue-user
+ user:
+ token: blue-token
+- name: green-user
+ user:
+ client-certificate: path/to/my/client/cert
+ client-key: path/to/my/client/key
+- name: jwt-user
+ user:
+ auth-provider:
+ config:
+ client-id: tectonic
+ client-secret: secret
+ extra-scopes: groups
+ id-token: jwt-token
+ idp-certificate-authority-data: data
+ idp-issuer-url: https://xyz/identity
+ refresh-token: refresh
+ name: oidc
+- name: gke-user
+ user:
+ auth-provider:
+ config:
+ access-token: myAccessToken
+ cmd-args: config config-helper --format=json
+ cmd-path: /home/user/google-cloud-sdk/bin/gcloud
+ expiry: 2018-03-04T14:08:18Z
+ expiry-key: '{.credential.token_expiry}'
+ token-key: '{.credential.access_token}'
+ name: gcp
+- name: string-date-gke-user
+ user:
+ auth-provider:
+ config:
+ access-token: myAccessToken
+ cmd-args: config config-helper --format=json
+ cmd-path: /home/user/google-cloud-sdk/bin/gcloud
+ expiry: "2018-03-04T14:08:18Z"
+ expiry-key: '{.credential.token_expiry}'
+ token-key: '{.credential.access_token}'
+ name: gcp
+- name: other-date-gke-user
+ user:
+ auth-provider:
+ config:
+ cmd-args: config config-helper --format=json
+ cmd-path: /home/user/google-cloud-sdk/bin/gcloud
+ expiry: "2018-03-04 14:08:18"
+ expiry-key: '{.credential.token_expiry}'
+ token-key: '{.credential.access_token}'
+ name: gcp
+"""
+ val ecConfigStr =
+ """
+apiVersion: v1
+clusters:
+- cluster:
+ certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJnRENDQVNlZ0F3SUJBZ0lVVWFxMkJNMFhaazBVb001OENGRXh2aEk0TWp3d0NnWUlLb1pJemowRUF3SXcKSFRFYk1Ca0dBMVVFQXhNU1ZVTlFJRU5zYVdWdWRDQlNiMjkwSUVOQk1CNFhEVEU0TURNeE9URTFOVEF3TUZvWApEVEl6TURNeE9ERTFOVEF3TUZvd0hURWJNQmtHQTFVRUF4TVNWVU5RSUVOc2FXVnVkQ0JTYjI5MElFTkJNRmt3CkV3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFa3pNY2JrNFRNc3lVcWcyYklKL050c2hCemxWcDcrenQKZ0trVHdHbGdYb09rZ3l3ckNBaU1YWnk4SG96dFE2NXJ3dDV1bUI1S0xXL3hSUi9vNExPclNxTkZNRU13RGdZRApWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFJd0hRWURWUjBPQkJZRUZKc2g0cTlvCkpZV09vMGsxdGJqQlpDbkM1eFdvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSURlMmpwR0ptWlNTL0tISGxmSnEKdnU5YXVzZCs5Nk5rR0g1SGFyWEN0azRtQWlCSnlUSUYyZk5aZ2xzZEc3USs0aG5TZ21EeEgzWUd0K0RjVzJiZwpiY0VlcFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCYWpDQ0FSQ2dBd0lCQWdJVVpaTTJPUFQwbTQxRGZDczFMRm5wYnNhL3hZb3dDZ1lJS29aSXpqMEVBd0l3CkV6RVJNQThHQTFVRUF4TUljM2RoY20wdFkyRXdIaGNOTVRnd016RTVNVFUxTURBd1doY05Nemd3TXpFME1UVTEKTURBd1dqQVRNUkV3RHdZRFZRUURFd2h6ZDJGeWJTMWpZVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSApBMElBQk5zVUo1YnhvRWZuNVVXS21TQ3Zoc3NlcDdubkpPa1dLUFVLaXgzSnhvbzlNNHp1WUVCdkpFV0VacmJnCmJyVWNPMHZyM3BWemxBUm83TXJZbk1MS09TbWpRakJBTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTdGhPTHVMSXNXL2pPOHcwSjJYM3hDM0FVY1FEQUtCZ2dxaGtqTwpQUVFEQWdOSUFEQkZBaUVBOTQwcGJxREJ6aGorTXNIMlhDUWRpUnJVQkFmTzVkV0YrdWFaUElnOHBHOENJSFF5ClNRQjhFS2wzcmZPVnpSOS9mU3FINm9kYVZQQk1GK3lqWk5VYnhFREgKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
+ server: https://horse.org:4443
+ name: horse-cluster
+contexts:
+- context:
+ cluster: horse-cluster
+ namespace: chisel-ns
+ user: rsa-user
+ name: federal-context
+current-context: federal-context
+kind: Config
+preferences:
+ colors: true
+users:
+- name: rsa-user
+ user:
+ client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNTRENDQVRDZ0F3SUJBZ0lJRlJwWFNVQ0VkSTh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBek1Ea3hPVEk1TlRaYUZ3MHhPVEF6TURreE9USTVOVFphTURNeApGREFTQmdOVkJBb1RDMFJ2WTJ0bGNpQkpibU11TVJzd0dRWURWUVFERXhKa2IyTnJaWEl0Wm05eUxXUmxjMnQwCmIzQXdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBTkZFRnRKT3VLS045VmtRKzJ5V0Z6d08KQUJPZ2hRM3lpSExBUkpQOHBxWHRDQ3VUV05weHdiUnM5TjlQcnhTbjBCblZzeXlreGlRNk12cHpLOWtDeWxBTgovWDZPbzFqWXgvK1BYdHp1NDAxc3VwbkhzSXI5S1VNQXhHVEdOK0NieXlRL3ZwTDlNSnVEV1VLUU1HYUtjNkFTCk5OdkEwVUVNWENQSTQrMHN0ZlFCQWdNQkFBR2pBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBOFdtNk4KdWk1cC9URlBURHRsczRpdm93cWlhbTR2MVM5aTVtMitSQXBCRUZralpXek0xVDhRZ2dUc1FsdDY2cGhYR0h2VwphenBKYzd3ajQzN082aURnQ0UwdXFiYmQ3bGRPNk1vb1Z6azNTaE5rU2YrUVNQd3dRdzlBQlRNR01JcC9qYzRFClk1S0Y1dG5iQTl6b3RTWUpid1JaVG1JQUVTSVQydFhKaWlyUFBLTXI3ekhTVkNpZVJWM1JmMWUwNFBCb3JnOUoKLzVoZGVzNDRUWEdiSSt3OURqaHV2ZGhRN0h2REdsdjZ6MmpsSy9hYXNxQXNoalFtVW9Hd0REelBsbGdkUm5adgp2cWd2WnovSVZNcVY5eEEzb2ZDOUwxUGF1ekFGdExjNHVZTFhFa1JsR2dFcVA2N2RjbVlxZFJWQXA4WkVBLzVqCk05aXFNdk11NnN5c3hTQWEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
+ client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWEFJQkFBS0JnUURSUkJiU1RyaWlqZlZaRVB0c2xoYzhEZ0FUb0lVTjhvaHl3RVNUL0thbDdRZ3JrMWphCmNjRzBiUFRmVDY4VXA5QVoxYk1zcE1Za09qTDZjeXZaQXNwUURmMStqcU5ZMk1mL2oxN2M3dU5OYkxxWng3Q0sKL1NsREFNUmt4amZnbThza1A3NlMvVENiZzFsQ2tEQm1pbk9nRWpUYndORkJERndqeU9QdExMWDBBUUlEQVFBQgpBb0dBU1daVGh1S2J1bENHalAzNjRpUm04K2FKT2xra01qY3VpdWxMWklqS3Z3bzd3bVVGVm1GdUt1WElvZ2NtCkJ0MnhqVTQ2Y1Y4K0xIakpaclU4M1Bvd2tXOHQycVE5aFdhZkdlbVY0bWhuYjFEYWNnRlNPMjZscytFT0NzODQKTDJONHR6UnpmTVFYZHd1cG56U1RCNjRsV1hDbjN3WS9kVm0yQUg5QlN2NVY2cDBDUVFEYmZMSlh1ZWlNRU1XOAptcE5zMVJBejE2MmhmWUxHVU5idFhCSk5xcm8xUTRtUS81enlCbWovUGFnSTROWWh3amxmSWZBbUVQcW5uanVHCmlqM0lKV29MQWtFQTlCUWE4R0dURnRkK2djczVzNmtOejFpbWdMR2FrRGRWcStHZitzaktvejc1WFg3c0tLeXkKTE1uVzE4ZzlKaGhSL3d1UzJzVlFNU1EwK2l6dld1NnRvd0pCQUo0M1V5L2R1WDVPRU53VjZUUEltcmRrUDZ0cgptRHR3eHAydmd4b3RlYkV2a0JqUHljakZTaWJEd1Q4MUkrYU41V0ZvUzM2Rk9zcGRTN2QrSzI3OVdXVUNRR2RpCjNNWlZqbWh1ZnplYlRhVzhSZzArRDhrVGNkVUVtMVZqRE5DOW5KZnBaTmNsbkFMZW85bzA1THdpSlVTdHFJM1AKNlRTaHY0WVJRQjk0U1NyTFR1RUNRQXRKOVYvVUg3WTl4cTlkd3JzVkZTM2FFTlFDTitsQThWQjBjcWZOSDlpUgpORFlZRTdGblJQV244VmlNdndsT3NzNmVUTjhIWGRrbXo2Yy8vV0NycENNPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
+"""
+ val pkcs8str =
+ """
+apiVersion: v1
+clusters:
+- cluster:
+ certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJnRENDQVNlZ0F3SUJBZ0lVVWFxMkJNMFhaazBVb001OENGRXh2aEk0TWp3d0NnWUlLb1pJemowRUF3SXcKSFRFYk1Ca0dBMVVFQXhNU1ZVTlFJRU5zYVdWdWRDQlNiMjkwSUVOQk1CNFhEVEU0TURNeE9URTFOVEF3TUZvWApEVEl6TURNeE9ERTFOVEF3TUZvd0hURWJNQmtHQTFVRUF4TVNWVU5RSUVOc2FXVnVkQ0JTYjI5MElFTkJNRmt3CkV3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFa3pNY2JrNFRNc3lVcWcyYklKL050c2hCemxWcDcrenQKZ0trVHdHbGdYb09rZ3l3ckNBaU1YWnk4SG96dFE2NXJ3dDV1bUI1S0xXL3hSUi9vNExPclNxTkZNRU13RGdZRApWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFJd0hRWURWUjBPQkJZRUZKc2g0cTlvCkpZV09vMGsxdGJqQlpDbkM1eFdvTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSURlMmpwR0ptWlNTL0tISGxmSnEKdnU5YXVzZCs5Nk5rR0g1SGFyWEN0azRtQWlCSnlUSUYyZk5aZ2xzZEc3USs0aG5TZ21EeEgzWUd0K0RjVzJiZwpiY0VlcFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCYWpDQ0FSQ2dBd0lCQWdJVVpaTTJPUFQwbTQxRGZDczFMRm5wYnNhL3hZb3dDZ1lJS29aSXpqMEVBd0l3CkV6RVJNQThHQTFVRUF4TUljM2RoY20wdFkyRXdIaGNOTVRnd016RTVNVFUxTURBd1doY05Nemd3TXpFME1UVTEKTURBd1dqQVRNUkV3RHdZRFZRUURFd2h6ZDJGeWJTMWpZVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSApBMElBQk5zVUo1YnhvRWZuNVVXS21TQ3Zoc3NlcDdubkpPa1dLUFVLaXgzSnhvbzlNNHp1WUVCdkpFV0VacmJnCmJyVWNPMHZyM3BWemxBUm83TXJZbk1MS09TbWpRakJBTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTdGhPTHVMSXNXL2pPOHcwSjJYM3hDM0FVY1FEQUtCZ2dxaGtqTwpQUVFEQWdOSUFEQkZBaUVBOTQwcGJxREJ6aGorTXNIMlhDUWRpUnJVQkFmTzVkV0YrdWFaUElnOHBHOENJSFF5ClNRQjhFS2wzcmZPVnpSOS9mU3FINm9kYVZQQk1GK3lqWk5VYnhFREgKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
+ server: https://horse.org:4443
+ name: horse-cluster
+contexts:
+- context:
+ cluster: horse-cluster
+ namespace: chisel-ns
+ user: rsa-user
+ name: federal-context
+current-context: federal-context
+kind: Config
+preferences:
+ colors: true
+users:
+- name: rsa-user
+ user:
+ client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNTRENDQVRDZ0F3SUJBZ0lJRlJwWFNVQ0VkSTh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBek1Ea3hPVEk1TlRaYUZ3MHhPVEF6TURreE9USTVOVFphTURNeApGREFTQmdOVkJBb1RDMFJ2WTJ0bGNpQkpibU11TVJzd0dRWURWUVFERXhKa2IyTnJaWEl0Wm05eUxXUmxjMnQwCmIzQXdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBTkZFRnRKT3VLS045VmtRKzJ5V0Z6d08KQUJPZ2hRM3lpSExBUkpQOHBxWHRDQ3VUV05weHdiUnM5TjlQcnhTbjBCblZzeXlreGlRNk12cHpLOWtDeWxBTgovWDZPbzFqWXgvK1BYdHp1NDAxc3VwbkhzSXI5S1VNQXhHVEdOK0NieXlRL3ZwTDlNSnVEV1VLUU1HYUtjNkFTCk5OdkEwVUVNWENQSTQrMHN0ZlFCQWdNQkFBR2pBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBOFdtNk4KdWk1cC9URlBURHRsczRpdm93cWlhbTR2MVM5aTVtMitSQXBCRUZralpXek0xVDhRZ2dUc1FsdDY2cGhYR0h2VwphenBKYzd3ajQzN082aURnQ0UwdXFiYmQ3bGRPNk1vb1Z6azNTaE5rU2YrUVNQd3dRdzlBQlRNR01JcC9qYzRFClk1S0Y1dG5iQTl6b3RTWUpid1JaVG1JQUVTSVQydFhKaWlyUFBLTXI3ekhTVkNpZVJWM1JmMWUwNFBCb3JnOUoKLzVoZGVzNDRUWEdiSSt3OURqaHV2ZGhRN0h2REdsdjZ6MmpsSy9hYXNxQXNoalFtVW9Hd0REelBsbGdkUm5adgp2cWd2WnovSVZNcVY5eEEzb2ZDOUwxUGF1ekFGdExjNHVZTFhFa1JsR2dFcVA2N2RjbVlxZFJWQXA4WkVBLzVqCk05aXFNdk11NnN5c3hTQWEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
+ client-key-data: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR0hBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJHMHdhd0lCQVFRZ2s1UFVqN08waGJ5VWNscFIKMTArQWNod3d4ZjZabWZmZnEyYjNBUVJjWE5LaFJBTkNBQVFMNU4zYSt1eVZIcThrZ0wwMGZjeVhwTllhc2hUMAowZVd6WGFNbmFhWWszcklMSnkzVG5LaU9Gelh0RnhsT3hUcFFEdXVmSTVsMEdHbkFsNE9KTXNqMAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==
+"""
+
+ val execConfigStr =
+ """
+apiVersion: v1
+clusters:
+- cluster:
+ certificate-authority-data: YXNkYWRhc2Q=
+ server: https://3.3.3.3
+ name: some-aws:cluster/test-cluster
+
+contexts:
+- context:
+ cluster: some-aws:cluster/test-cluster
+ user: some-aws:cluster/test-cluster
+ name: some-aws:cluster/test-cluster
+
+current-context: some-aws:cluster/test-cluster
+kind: Config
+preferences: {}
+users:
+- name: some-aws:cluster/test-cluster
+ user:
+ exec:
+ apiVersion: client.authentication.k8s.io/v1beta1
+ args:
+ - --region
+ - us-east-1
+ - eks
+ - get-token
+ - --cluster-name
+ - test-cluster
+ - --output
+ - json
+ command: aws
+ env:
+ - name: AWS_PROFILE
+ value: default
+ interactiveMode: IfAvailable
+ provideClusterInfo: false
+
+"""
+
+}
diff --git a/client/src/test/scala/skuber/json/PodFormatSpec.scala b/client/src/test/scala/skuber/json/PodFormatSpec.scala
index eb63841a..1a55c5f6 100644
--- a/client/src/test/scala/skuber/json/PodFormatSpec.scala
+++ b/client/src/test/scala/skuber/json/PodFormatSpec.scala
@@ -482,6 +482,7 @@ import Pod._
import NodeAffinity.{PreferredSchedulingTerm, PreferredSchedulingTerms}
val affinityJsonSource = Source.fromURL(getClass.getResource("/exampleAffinityNoRequirements.json"))
+
val affinityJsonStr = affinityJsonSource.mkString
val myAffinity = Json.parse(affinityJsonStr).as[Affinity]
diff --git a/examples/src/main/resources/application.conf b/examples/src/main/resources/application.conf
index 22d236d1..d938c6b9 100644
--- a/examples/src/main/resources/application.conf
+++ b/examples/src/main/resources/application.conf
@@ -10,3 +10,16 @@ pekko {
}
}
}
+
+pekko.http {
+ host-connection-pool {
+ base-connection-backoff = 1s
+ max-connection-backoff = 100s
+ max-connections = 1024
+ max-open-requests = 2048
+ }
+ client.connecting-timeout = 1200s
+ client.idle-timeout = 1200s
+ parsing.max-chunk-size = 64m
+}
+