It's scala, It's purely functional, monadic.
Cable is functionality-rich, task-centric, succinct, handy. Cable Supports ssh proxying, jumping over networks, tasks chaining.
We support $HOME/.ssh config as well as the global ssh client config.
libraryDependencies += "io.github.zhongwm" %% "cable" % "0.4.1"
Your host behind a bastion machine? You have a series of remote tasks to deal with? no problem. And connections are reused by multiple tasks for the same machine.
A DSL to represent composite ssh tasks.
import cable.zssh.TypeDef._
import HostConnS._
import cable.zssh.Zssh._
val simpleTask =
Action("192.168.99.100", password = Some("password"), action = scriptIO("sleep 5; ls /"))
Most of the parameters can be omitted only the hostname or address (the first parameter) is required, We can use a private key instead of password, it can be your default .ssh rsa ssh key. Cable will read your ssh key from that file and use that key for authentication.
val simpleData =
Action(
"192.168.99.100"
action = scriptIO("hostname") <&>
scpUploadIO("build.sbt") <&
scpDownloadIO("/etc/issue")
)
To get multiple tasks on different hosts executed one after another, chain them up using +:
.
val simpleListTasks =
Action("192.168.99.100", Some(privateKey), action = scriptIO("cat /etc/issue")) +:
Action("192.168.99.100", port = Some(2023), username=Some("user"), password=("password"), scpDownloadIO("/etc/issue"))
With nested ssh tasks composing, parent level acts as the jumper host for the child tasks, also parent level tasks get gets executed before the latter.
val simpleNestedTasks = Parental(
JustConnect("192.168.99.100", username=Some("user"), password=Some("password")),
Action("192.168.99.100" password = Some("password"), action = scpUploadIO("build.sbt"))
)
val compoundTasks =
JustConnect("192.168.99.100", 2022, "user", "password") +:
Parental(
JustConnect("192.168.99.100", 2022, "user", "password" ),
Action("192.168.99.100", 2022, "user", "password", scriptIO("hostname")) +:
Action("192.168.99.100", 2022, "user", "password", scpUploadIO("build.sbt"))
) +:
Action("192.168.99.100", 2023, "user", "password", scpDownloadIO("/etc/issue"))
Tap on run
to fire task execution. Result types are inferred and reflecting the task composition
structure.
val nestedResult = simpleNestedTasks.run() // Inferred type: NestedC[Unit, (Int, (Chunk[String], Chunk[String]))]
val listResult = simpleListTasks.run() // Inferred type: (Int, (Chunk[String], Chunk[String])) +|: (Int, (Chunk[String], Chunk[String]))
As we can see in the previous sample code, we don't need to concern about connections' management, yet it's safely managed.
Connections are guaranteed to be released correctly
Full support for ZIO composition, ready to be embedded into ZIO project, compatible with ZIO ecosystem.
val action = {
scriptIO("hostname") <&>
scpUploadIO("build.sbt") <&
scpDownloadIO("/etc/issue")
}
val jumperLayer = Zssh.sessionL("192.168.99.100", 2022, username = Some("test"), password = Some("test"))
val jumpedLayer =
Zssh.jumpSessionL(jumperLayer, "192.168.99.100", 2023, Some("test"), Some("test"))
val layer2 =
((jumperLayer ++ Blocking.live) >>> Zssh.jumpAddressLayer("192.168.99.100", 2023)) ++ Blocking.live
val layer3 = layer2 >>> Zssh.jumpSshConnL(Some("test"), Some("test"))
val layer4 = (Zssh.clientLayer ++ layer3 ++ Blocking.live) >>> Zssh.sessionL
private val process = for {
connJump <- Zssh.make(
Left("192.168.99.100", 2022),
username = Some("test"),
password = Some("test")
)
rst <- connJump.sessionM { outerSession =>
Zssh.jumpTo("192.168.99.100", 2023)(outerSession) >>= { fwd =>
val conn = Zssh(Right(fwd.getBoundAddress), Some("test"), password = Some("test"))
conn.sessionM { innerSession =>
Zssh.script("hostname")(innerSession) <&>
Zssh.scpUpload("build.sbt")(innerSession) <&
Zssh.scpDownload("/etc/issue")(innerSession)
}
}
}
_ <- putStrLn(rst._1._2._1.mkString)
_ <- putStrLn(rst._1._2._2.mkString)
xc <- ZIO.succeed {
zio.ExitCode(rst._1._1)
}
} yield xc
Based on mina-sshd-netty
This project is greatly inspired by a famous python project ansible, which is a very famous project in devops. This project strives to join the functional world and the devops world in the field of remote host related tasks. Not all of them, not all of ansible, but in some way.