Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions client/src/main/scala/skuber/api/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,11 @@ object Configuration {
def valueAt[T](parent: YamlMap, key: String, fallback: Option[T] = None) : T =
parent.asScala.get(key).orElse(fallback).get.asInstanceOf[T]

def instantValueAt[T](parent: YamlMap, key: String) : Instant =
parent.asScala.get(key) match {
case Some(d: Date) => d.toInstant
case Some(s: String) => parseInstant(s)
case Some(unsupported) => sys.error(s"Unsupported date type: $unsupported")
case None => sys.error(s"No value found at $key")
def instantValueAt[T](parent: YamlMap, key: String) : Option[Instant] =
parent.asScala.get(key).map {
case d: Date => d.toInstant
case s: String => parseInstant(s)
case unsupported => sys.error(s"Unsupported date type: $unsupported")
}

def optionalValueAt[T](parent: YamlMap, key: String) : Option[T] =
Expand Down Expand Up @@ -175,7 +174,7 @@ object Configuration {
case "gcp" =>
Some(
GcpAuth(
accessToken = valueAt(config, "access-token"),
accessToken = optionalValueAt(config, "access-token"),
expiry = instantValueAt(config, "expiry"),
cmdPath = valueAt(config, "cmd-path"),
cmdArgs = valueAt(config, "cmd-args")
Expand Down
29 changes: 20 additions & 9 deletions client/src/main/scala/skuber/api/client/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ package object client {
final case class GcpAuth private(private val config: GcpConfiguration) extends AuthProviderAuth {
override val name = "gcp"

@volatile private var refresh: GcpRefresh = new GcpRefresh(config.accessToken, config.expiry)
@volatile private var refresh: GcpRefresh = config.refresh.map(r => DefinedGcpRefresh(r.accessToken, r.expiry))
.getOrElse(EmptyGcpRefresh)

def refreshGcpToken(): GcpRefresh = {
val output = config.cmd.execute()
Expand All @@ -115,18 +116,29 @@ package object client {
"""GcpAuth(accessToken=<redacted>)""".stripMargin
}

final private[client] case class GcpRefresh(accessToken: String, expiry: Instant) {
def expired: Boolean = Instant.now.isAfter(expiry.minusSeconds(20))
private[client] sealed trait GcpRefresh {
def expired: Boolean
def accessToken: String
}

private[client] final case class DefinedGcpRefresh(accessToken: String, expiry: Instant) extends GcpRefresh {
override def expired: Boolean = Instant.now.isAfter(expiry.minusSeconds(20))
}
private[client] final object EmptyGcpRefresh extends GcpRefresh {
override def expired: Boolean = true
override def accessToken: String = throw new IllegalStateException("access-token has never been refreshed")
}

private[client] object GcpRefresh {
implicit val gcpRefreshReads: Reads[GcpRefresh] = (
(JsPath \ "credential" \ "access_token").read[String] and
(JsPath \ "credential" \ "token_expiry").read[Instant]
) (GcpRefresh.apply _)
) (DefinedGcpRefresh.apply _)
}

final case class GcpConfiguration(accessToken: String, expiry: Instant, cmd: GcpCommand)
final case class GcpConfiguration(refresh: Option[GcpConfiguredRefresh], cmd: GcpCommand)

final case class GcpConfiguredRefresh(accessToken: String, expiry: Instant)

final case class GcpCommand(cmd: String, args: String) {

Expand All @@ -136,12 +148,11 @@ package object client {
}

object GcpAuth {
def apply(accessToken: String, expiry: Instant, cmdPath: String, cmdArgs: String): GcpAuth =
def apply(accessToken: Option[String], expiry: Option[Instant], cmdPath: String, cmdArgs: String): GcpAuth =
new GcpAuth(
GcpConfiguration(
accessToken = accessToken,
expiry = expiry,
GcpCommand(cmdPath, cmdArgs)
refresh = accessToken.zip(expiry).map(GcpConfiguredRefresh.tupled).headOption,
cmd = GcpCommand(cmdPath, cmdArgs)
)
)
}
Expand Down
56 changes: 54 additions & 2 deletions client/src/test/scala/skuber/api/ConfigurationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import java.time.Instant

import akka.stream.ActorMaterializer
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory

import scala.util.Try
import skuber.api.client._
Expand Down Expand Up @@ -109,7 +108,7 @@ users:
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 = "myAccessToken", expiry = Instant.parse("2018-03-04T14:08:18Z"),
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 users=Map("blue-user"->blueUser,"green-user"->greenUser,"jwt-user"->jwtUser, "gke-user"->gcpUser, "string-date-gke-user"->gcpUser)

Expand All @@ -126,6 +125,59 @@ users:
directlyConstructedConfig mustEqual parsedFromStringConfig
}

"A kubeconfig file without access-token can be parsed correctly" >> {
val kubeConfigWoAccessToken =
"""
|apiVersion: v1
|clusters:
|- cluster:
| insecure-skip-tls-verify: true
| server: https://cluster.org:443
| name: the-cluster
|contexts:
|- context:
| cluster: the-cluster
| namespace: the-ns
| user: the-user
| name: the-context
|current-context: the-context
|kind: Config
|users:
|- name: the-user
| user:
| auth-provider:
| config:
| cmd-args: config config-helper --format=json
| cmd-path: /home/user/google-cloud-sdk/bin/gcloud
| expiry-key: '{.credential.token_expiry}'
| token-key: '{.credential.access_token}'
| name: gcp
|""".stripMargin
val is = new java.io.ByteArrayInputStream(kubeConfigWoAccessToken.getBytes(java.nio.charset.Charset.forName("UTF-8")))
val k8sConfig = K8SConfiguration.parseKubeconfigStream(is)
val parsedFromStringConfig = k8sConfig.get

// construct equivalent config directly for comparison

val theCluster=K8SCluster("v1", "https://cluster.org:443", true)
val clusters=Map("the-cluster" -> theCluster)

val theUser = GcpAuth(accessToken = None, expiry = None,
cmdPath = "/home/user/google-cloud-sdk/bin/gcloud", cmdArgs = "config config-helper --format=json")
val users=Map("the-user" -> theUser)

val theContext=K8SContext(theCluster, theUser, Namespace.forName("the-ns"))
val contexts=Map("the-context" -> theContext)

val directlyConstructedConfig=Configuration(clusters,contexts,theContext,users)
directlyConstructedConfig.clusters mustEqual parsedFromStringConfig.clusters
directlyConstructedConfig.contexts mustEqual parsedFromStringConfig.contexts
directlyConstructedConfig.users mustEqual parsedFromStringConfig.users
directlyConstructedConfig.currentContext mustEqual parsedFromStringConfig.currentContext

directlyConstructedConfig mustEqual parsedFromStringConfig
}

"Parse EC private keys from kubeconfig file" >> {
val ecConfigStr = """
apiVersion: v1
Expand Down
2 changes: 1 addition & 1 deletion client/src/test/scala/skuber/model/AuthSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AuthSpec extends Specification {
}

"GcpAuth toString masks accessToken" >> {
GcpAuth(accessToken = "MyAccessToken", expiry = Instant.now, cmdPath = "gcp", cmdArgs = "").toString mustEqual
GcpAuth(accessToken = Some("MyAccessToken"), expiry = Some(Instant.now), cmdPath = "gcp", cmdArgs = "").toString mustEqual
"GcpAuth(accessToken=<redacted>)"
}

Expand Down