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 +} +