+
Skip to content
Open
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This example lists pods in `kube-system` namespace:
implicit val materializer = ActorMaterializer()
implicit val dispatcher = system.dispatcher

val k8s = k8sInit()
val k8s = k8sInit
val listPodsRequest = k8s.listInNamespace[PodList]("kube-system")
listPodsRequest.onComplete {
case Success(pods) => pods.items.foreach { p => println(p.name) }
Expand Down Expand Up @@ -66,7 +66,7 @@ Provides you with a configured client on startup. It is handy to use this for qu
> Just handy shortcut to import skuber inside ammonite-repl:

```scala
import $ivy.`io.skuber::skuber:2.0.12`, skuber._, skuber.json.format._
import $ivy.`io.skuber::skuber:2.1.1`, skuber._, skuber.json.format._
```

### Interactive with sbt
Expand Down Expand Up @@ -119,7 +119,7 @@ To get minikube follow the instructions [here](https://github.com/kubernetes/min
You can use the latest release (for Scala 2.11 or 2.12) by adding to your build:

```sbt
libraryDependencies += "io.skuber" %% "skuber" % "2.0.12"
libraryDependencies += "io.skuber" %% "skuber" % "2.1.1"
```

Meanwhile users of skuber v1 can continue to use the latest (and possibly final, with exception of important fixes) v1.x release, which is available only on Scala 2.11:
Expand Down
5 changes: 3 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases/

val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.14.0"
val specs2 = "org.specs2" %% "specs2-core" % "4.3.2"
val specs2mock = "org.specs2" %% "specs2-mock" % "4.3.2"
val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
val mockito = "org.mockito" % "mockito-core" % "2.21.0"
val akkaStreamTestKit = "com.typesafe.akka" %% "akka-stream-testkit" % "2.5.14"
Expand All @@ -29,7 +30,7 @@ scalacOptions += "-target:jvm-1.8"

scalacOptions in Test ++= Seq("-Yrangepos")

version in ThisBuild := "2.0.12"
version in ThisBuild := "2.1.1"

sonatypeProfileName := "io.skuber"

Expand Down Expand Up @@ -65,7 +66,7 @@ lazy val skuberSettings = Seq(
name := "skuber",
libraryDependencies ++= Seq(
akkaHttp, akkaStream, playJson, snakeYaml, commonsIO, commonsCodec, bouncyCastle,
scalaCheck % Test, specs2 % Test, mockito % Test, akkaStreamTestKit % Test,
scalaCheck % Test, specs2 % Test, specs2mock % Test, mockito % Test, akkaStreamTestKit % Test,
scalaTest % Test
).map(_.exclude("commons-logging", "commons-logging"))
)
Expand Down
5 changes: 3 additions & 2 deletions client/src/main/scala/skuber/Pod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ object Pod {
priorityClassName: Option[String] = None,
schedulerName: Option[String] = None,
subdomain: Option[String] = None,
dnsConfig: Option[DNSConfig] = None) {

dnsConfig: Option[DNSConfig] = None,
shareProcessNamespace: Option[Boolean] = None) {

// a few convenience methods for fluently building out a pod spec
def addContainer(c: Container) = { this.copy(containers = c :: containers) }
def addInitContainer(c: Container) = { this.copy(initContainers = c :: initContainers) }
Expand Down
5 changes: 4 additions & 1 deletion client/src/main/scala/skuber/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ case class Service(
def withClusterIP(ip: String) = this.copy(spec = Some(copySpec.copy(clusterIP = ip)))
def withType(_type: Service.Type.Value) = this.copy(spec = Some(copySpec.copy(_type = _type)))
def withLoadBalancerType = withType(Service.Type.LoadBalancer)
def withLoadBalancerIP(ip: String) = this.copy(spec = Some(copySpec.copy(loadBalancerIP = ip)))

def withExternalIP(ip: String) = this.copy(spec = Some(copySpec.copy(externalIPs = List(ip))))
def withExternalIPs(ips: List[String]) = this.copy(spec = Some(copySpec.copy(externalIPs = ips)))
Expand Down Expand Up @@ -108,7 +109,9 @@ object Service {
_type: ServiceType=ClusterIP,
externalIPs: List[String] = List(),
externalName: String = "",
sessionAffinity: Affinity.Affinity = Affinity.None)
sessionAffinity: Affinity.Affinity = Affinity.None,
loadBalancerIP: String = ""
)
{
def withSelector(sel: Map[String, String]) : Spec = this.copy(selector = sel)
def withSelector(sel: Tuple2[String,String]): Spec = withSelector(Map(sel))
Expand Down
25 changes: 22 additions & 3 deletions client/src/main/scala/skuber/api/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,28 @@ object Configuration {
}
}

val maybeAuth = optionalValueAt[YamlMap](userConfig, "auth-provider") match {
case Some(authProvider) => authProviderRead(authProvider)
case None =>
def execAuthRead(execProvider: YamlMap): Option[AuthProviderAuth] = {
import scala.collection.JavaConverters._
for {
cmd <- optionalValueAt[String](execProvider, "command")
args = optionalValueAt[java.util.List[String]](execProvider, "args").getOrElse(new java.util.ArrayList())
env = optionalValueAt[java.util.List[YamlMap]](execProvider, "env").getOrElse(new java.util.ArrayList()).asScala.flatMap(
m => for {
name <- optionalValueAt[String](m, "name")
value <- optionalValueAt[String](m, "value")
} yield name -> value
)
} yield ExecAuth(
command = cmd,
args = args.asScala,
env = env
)
}

val maybeAuth =
optionalValueAt[YamlMap](userConfig, "exec").flatMap(execAuthRead)
.orElse(optionalValueAt[YamlMap](userConfig, "auth-provider").flatMap(authProviderRead))
.orElse {
val clientCertificate = pathOrDataValueAt(userConfig, "client-certificate", "client-certificate-data")
val clientKey = pathOrDataValueAt(userConfig, "client-key", "client-key-data")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,9 @@ trait KubernetesClient {
* @param maybeContainerName an optional container name
* @param maybeStdin optional Akka Source for sending input to stdin for the command
* @param maybeStdout optional Akka Sink to receive output from stdout for the command
* @param maybeStderr optional Akka Sink to recieve output from stderr for the command
* @param maybeStderr optional Akka Sink to receive output from stderr for the command
* @param tty optionally set tty on
* @param maybeClose if set, this Promise can be used to terminate the command
* @param maybeClose if set, this can be used to close the connection to the pod by completing the promise
* @return A future indicating the exec command has been submitted
*/
def exec(
Expand Down
76 changes: 69 additions & 7 deletions client/src/main/scala/skuber/api/client/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,67 @@ package object client {
override def toString = """OidcAuth(idToken=<redacted>)"""
}

final case class GcpAuth private(private val config: GcpConfiguration) extends AuthProviderAuth {
final case class ExecAuth private(private[api] val cmd: ExecAuthCommand, executioner: CommandExecutioner) extends AuthProviderAuth {
override def name: String = "exec"

def command = cmd.command

@volatile private var refresh: ExecRefresh = new ExecRefresh("", None)

def refreshToken(): ExecRefresh = {
val output = executioner.execute(
command = cmd.command +: cmd.args,
env = cmd.env
)
Json.parse(output).as[ExecRefresh]
}

def accessToken: String = this.synchronized {
if(refresh.expired)
refresh = refreshToken()
refresh.accessToken
}

override def toString = """ExecAuth(token=<redacted>)""".stripMargin
}

final private[client] case class ExecRefresh(accessToken: String, maybeExpiry: Option[Instant]) {
def expired: Boolean = !maybeExpiry.exists(expiry => Instant.now.isBefore(expiry.minusSeconds(20)))
}

private[client] object ExecRefresh {
implicit val execRefreshReads: Reads[ExecRefresh] = (
(JsPath \ "status" \ "token").read[String] and
(JsPath \ "status" \ "expirationTimestamp").readNullable[Instant]
)(ExecRefresh.apply _)
}

trait CommandExecutioner {
def execute(command: Seq[String], env: Seq[(String,String)]): String
}

implicit val defaultCommandExecution = new CommandExecutioner {
override def execute(command: Seq[String], env: Seq[(String, String)]): String = {
scala.sys.process.Process(
command = command,
cwd = None,
extraEnv = env:_*
).!!
}
}

final case class GcpAuth private(private val config: GcpConfiguration, executioner: CommandExecutioner) extends AuthProviderAuth {
override val name = "gcp"

def command = config.cmd.cmd

@volatile private var refresh: GcpRefresh = new GcpRefresh(config.accessToken, config.expiry)

def refreshGcpToken(): GcpRefresh = {
val output = config.cmd.execute()
val output = executioner.execute(
command = config.cmd.cmd +: config.cmd.args.split("""\s+""").toSeq,
env = Seq.empty
)
Json.parse(output).as[GcpRefresh]
}

Expand Down Expand Up @@ -128,21 +182,29 @@ package object client {

final case class GcpConfiguration(accessToken: String, expiry: Instant, cmd: GcpCommand)

final case class GcpCommand(cmd: String, args: String) {
final case class ExecAuthCommand(command: String, args: Seq[String], env: Seq[(String,String)])

import scala.sys.process._
final case class GcpCommand(cmd: String, args: String)

def execute(): String = s"$cmd $args".!!
object ExecAuth {
def apply(command: String, args: Seq[String], env: Seq[(String,String)])
(implicit executioner: CommandExecutioner): ExecAuth =
new ExecAuth(
cmd = ExecAuthCommand(command, args, env),
executioner
)
}

object GcpAuth {
def apply(accessToken: String, expiry: Instant, cmdPath: String, cmdArgs: String): GcpAuth =
def apply(accessToken: String, expiry: Instant, cmdPath: String, cmdArgs: String)
(implicit executioner: CommandExecutioner): GcpAuth =
new GcpAuth(
GcpConfiguration(
accessToken = accessToken,
expiry = expiry,
GcpCommand(cmdPath, cmdArgs)
)
),
executioner
)
}

Expand Down
10 changes: 7 additions & 3 deletions client/src/main/scala/skuber/batch/Job.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import skuber.{LabelSelector, NonCoreResourceSpecification, ObjectMeta, ObjectRe
/**
* @author Cory Klein
*/
case class Job(val kind: String ="Job",
case class Job(val kind: String = "Job",
override val apiVersion: String = batchAPIVersion,
val metadata: ObjectMeta = ObjectMeta(),
spec: Option[Job.Spec] = None,
status: Option[Job.Status] = None) extends ObjectResource {

lazy val copySpec: Job.Spec = this.spec.getOrElse(new Job.Spec())

def withTemplate(template: Pod.Template.Spec) = this.copy(spec=Some(copySpec.copy(template=Some(template))))
def withTemplate(template: Pod.Template.Spec) = this.copy(spec = Some(copySpec.copy(template = Some(template))))
def withParallelism(parallelism: Int) = this.copy(spec = Some(copySpec.copy(parallelism = Some(parallelism))))
def withCompletions(completions: Int) = this.copy(spec = Some(copySpec.copy(completions = Some(completions))))
def withActiveDeadlineSeconds(seconds: Int) = this.copy(spec = Some(copySpec.copy(activeDeadlineSeconds = Some(seconds))))
def withBackoffLimit(limit: Int) = this.copy(spec = Some(copySpec.copy(backoffLimit = Some(limit))))
}

object Job {
Expand Down Expand Up @@ -43,7 +47,7 @@ object Job {
template: Option[Pod.Template.Spec] = None,
backoffLimit: Option[Int] = None)

case class Status(conditions: Option[Condition] = None,
case class Status(conditions: List[Condition] = List(),
startTime: Option[Timestamp] = None,
completionTime: Option[Timestamp] = None,
active: Option[Int] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ package object format {
)(Job.Condition.apply _, unlift(Job.Condition.unapply))

implicit val jobStatusFormat: Format[Job.Status] = (
(JsPath \ "conditions").formatNullable[Job.Condition] and
(JsPath \ "conditions").formatMaybeEmptyList[Job.Condition] and
(JsPath \ "startTime").formatNullable[Timestamp] and
(JsPath \ "completionTime").formatNullable[Timestamp] and
(JsPath \ "active").formatNullable[Int] and
Expand Down
15 changes: 9 additions & 6 deletions client/src/main/scala/skuber/json/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ package object format {
(JsPath \ "securityContext").formatNullable[PodSecurityContext]
).tupled

val podSpecPartTwoFormat: OFormat[(Option[String], List[Pod.HostAlias], Option[Boolean], Option[Boolean], Option[Boolean], Option[Int], Option[String], Option[String], Option[String], Option[Pod.DNSConfig])] = (
val podSpecPartTwoFormat: OFormat[(Option[String], List[Pod.HostAlias], Option[Boolean], Option[Boolean], Option[Boolean], Option[Int], Option[String], Option[String], Option[String], Option[Pod.DNSConfig], Option[Boolean])] = (
(JsPath \ "hostname").formatNullable[String] and
(JsPath \ "hostAliases").formatMaybeEmptyList[Pod.HostAlias] and
(JsPath \ "hostPID").formatNullable[Boolean] and
Expand All @@ -802,14 +802,15 @@ package object format {
(JsPath \ "priorityClassName").formatNullable[String] and
(JsPath \ "schedulerName").formatNullable[String] and
(JsPath \ "subdomain").formatNullable[String] and
(JsPath \ "dnsConfig").formatNullable[Pod.DNSConfig]
(JsPath \ "dnsConfig").formatNullable[Pod.DNSConfig] and
(JsPath \ "shareProcessNamespace").formatNullable[Boolean]
).tupled

implicit val podSpecFmt: Format[Pod.Spec] = (
podSpecPartOneFormat and podSpecPartTwoFormat
).apply({
case ((conts, initConts, vols, rpol, tgps, adls, dnspol, nodesel, svcac, node, hnet, ips, aff, tol, psc), (host, aliases, pid, ipc, asat, prio, prioc, sched, subd, dnsc)) =>
Pod.Spec(conts, initConts, vols, rpol, tgps, adls, dnspol, nodesel, svcac, node, hnet, ips, aff, tol, psc, host, aliases, pid, ipc, asat, prio, prioc, sched, subd, dnsc)
case ((conts, initConts, vols, rpol, tgps, adls, dnspol, nodesel, svcac, node, hnet, ips, aff, tol, psc), (host, aliases, pid, ipc, asat, prio, prioc, sched, subd, dnsc, spn)) =>
Pod.Spec(conts, initConts, vols, rpol, tgps, adls, dnspol, nodesel, svcac, node, hnet, ips, aff, tol, psc, host, aliases, pid, ipc, asat, prio, prioc, sched, subd, dnsc, spn)
}, s =>(
( s.containers,
s.initContainers,
Expand All @@ -836,7 +837,8 @@ package object format {
s.priorityClassName,
s.schedulerName,
s.subdomain,
s.dnsConfig
s.dnsConfig,
s.shareProcessNamespace
))
)

Expand Down Expand Up @@ -880,7 +882,8 @@ package object format {
(JsPath \ "type").formatEnum(Service.Type, Some(Service.Type.ClusterIP)) and
(JsPath \ "externalIPs").formatMaybeEmptyList[String] and
(JsPath \ "externalName").formatMaybeEmptyString() and
(JsPath \ "sessionAffinity").formatEnum(Service.Affinity, Some(Service.Affinity.None))
(JsPath \ "sessionAffinity").formatEnum(Service.Affinity, Some(Service.Affinity.None)) and
(JsPath \ "loadBalancerIP").formatMaybeEmptyString()
)(Service.Spec.apply _, unlift(Service.Spec.unapply))

implicit val serviceFmt: Format[Service] = (
Expand Down
1 change: 1 addition & 0 deletions client/src/test/resources/examplePodExtendedSpec.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"terminationGracePeriodSeconds": 60,
"hostNetwork": true,
"dnsPolicy": "None",
"shareProcessNamespace": true,
"imagePullSecrets": [
{
"name": "secret"
Expand Down
18 changes: 17 additions & 1 deletion client/src/test/scala/skuber/api/ConfigurationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ users:
expiry-key: '{.credential.token_expiry}'
token-key: '{.credential.access_token}'
name: gcp
- name: aws-user
user:
exec:
apiVersion: "client.authentication.k8s.io/v1alpha1"
command: "/usr/local/bin/heptio-authenticator-aws"
args: ["token", "-i", "CLUSTER_ID", "-r", "ROLE_ARN"]
env:
- name: "1"
value: "2"
- name: "3"
value: "4"
"""

implicit val system=ActorSystem("test")
Expand All @@ -100,7 +111,12 @@ users:
val jwtUser= OidcAuth(idToken = "jwt-token")
val gcpUser = GcpAuth(accessToken = "myAccessToken", expiry = 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)
val awsUser = ExecAuth(
command = "/usr/local/bin/heptio-authenticator-aws",
args = Seq("token", "-i", "CLUSTER_ID", "-r", "ROLE_ARN"),
env = Seq("1" -> "2", "3" -> "4")
)
val users=Map("blue-user"->blueUser,"green-user"->greenUser,"jwt-user"->jwtUser, "gke-user"->gcpUser, "aws-user"->awsUser)

val federalContext=K8SContext(horseCluster,greenUser,Namespace.forName("chisel-ns"))
val queenAnneContext=K8SContext(pigCluster,blueUser, Namespace.forName("saw-ns"))
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载