cable
cable copied to clipboard
Functional, Practical, a scala ssh library
Cable - scala ssh library
A SSH client lib that is Functional, Monadic
It's scala, It's purely functional, monadic.
Practical, Functionality rich
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.
Installation
libraryDependencies += "io.github.zhongwm" %% "cable" % "0.4.1"
Supports ssh proxying, in a monadic way!
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.
Simple ssh task
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.
Simple ssh task with multiple tasks on a same host or connection.
val simpleData =
Action(
"192.168.99.100"
action = scriptIO("hostname") <&>
scpUploadIO("build.sbt") <&
scpDownloadIO("/etc/issue")
)
Multiple ssh tasks example
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"))
Nested ssh tasks example
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"))
)
Compound example
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"))
Running
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]))
Resource Safe
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
Docs
Documentation
To get started
Full Support for ZIO programming
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
Efficient, fast
Based on mina-sshd-netty
P.S.
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.