Script monitoring
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package com.jaytux.phoebench.clients.cli
|
||||
|
||||
import com.github.ajalt.clikt.core.*
|
||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||
import com.github.ajalt.clikt.parameters.arguments.multiple
|
||||
import com.github.ajalt.clikt.parameters.groups.mutuallyExclusiveOptions
|
||||
import com.github.ajalt.clikt.parameters.groups.single
|
||||
import com.github.ajalt.clikt.parameters.options.*
|
||||
@@ -227,6 +229,24 @@ object CLI {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
class Monitor : CliktCommand(name = "monitor") {
|
||||
val finder by mutuallyExclusiveOptions<Project.IProjectIdentification>(
|
||||
option("--id", help = "Find a project by UUID.").convert { Project.ID(Uuid.parse(it)) },
|
||||
option("--name", help = "Find a project by name (formatted [user]/[project])").convert {
|
||||
val split = it.split('/')
|
||||
if(split.size != 2) throw IllegalArgumentException("Invalid format (expected [user]/[project])")
|
||||
Project.ProjectName(split[0], split[1])
|
||||
}
|
||||
).single()
|
||||
val command by argument("command", help = "The command to be run")
|
||||
val commandArgs by argument("arguments", help = "Arguments to pass to the command").multiple()
|
||||
|
||||
override fun run() {
|
||||
MonitorHandler.monitor(finder, command, commandArgs)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
class Version : CliktCommand(name = "version") {
|
||||
override fun run() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.jaytux.phoebench.clients.cli
|
||||
|
||||
import com.jaytux.phoebench.common.ApiRoute
|
||||
import com.jaytux.phoebench.common.CSERoute
|
||||
import com.jaytux.phoebench.common.Either
|
||||
import com.jaytux.phoebench.common.ErrorResponse
|
||||
import com.jaytux.phoebench.common.IClient
|
||||
@@ -9,15 +10,31 @@ import com.jaytux.phoebench.common.Routes
|
||||
import com.jaytux.phoebench.common.TokenResponse
|
||||
import com.jaytux.phoebench.common.error
|
||||
import com.jaytux.phoebench.common.foldSuspend
|
||||
import com.jaytux.phoebench.common.value
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.ResponseException
|
||||
import io.ktor.client.plugins.auth.Auth
|
||||
import io.ktor.client.plugins.auth.providers.BearerTokens
|
||||
import io.ktor.client.plugins.auth.providers.bearer
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.websocket.WebSocketException
|
||||
import io.ktor.client.plugins.websocket.WebSockets
|
||||
import io.ktor.client.plugins.websocket.sendSerialized
|
||||
import io.ktor.client.plugins.websocket.webSocket
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.util.reflect.TypeInfo
|
||||
import io.ktor.util.reflect.typeInfo
|
||||
import io.ktor.utils.io.CancellationException
|
||||
import io.ktor.websocket.CloseReason
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.URL
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object Client {
|
||||
@@ -68,6 +85,10 @@ object Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
install(WebSockets) {
|
||||
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -91,6 +112,19 @@ object Client {
|
||||
suspend fun <TReq: Any, TRes: Any> callRoute(route: ApiRoute<TReq, TRes>, body: TReq): Either<ErrorResponse, TRes> =
|
||||
callRoute(_client, route, body)
|
||||
|
||||
suspend fun <TParams: Any, TEvent: Any> callCSE(
|
||||
route: CSERoute<TParams, TEvent>, params: TParams,
|
||||
body: suspend (sender: suspend (TEvent) -> Unit) -> Unit
|
||||
): Either<ErrorResponse, Unit> {
|
||||
try {
|
||||
val client = IClient.Default(_client, _server ?: throw IllegalStateException("No server URL set."))
|
||||
return route.call(client, params, body)
|
||||
}
|
||||
catch(e: CancellationException) {
|
||||
return ErrorResponse("Event stream disconnected.").error()
|
||||
}
|
||||
}
|
||||
|
||||
fun onLogin(tokens: TokenResponse) {
|
||||
_refreshToken = tokens.refresh
|
||||
_accessToken = tokens.access
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.jaytux.phoebench.clients.cli
|
||||
|
||||
import com.github.pgreze.process.Redirect
|
||||
import com.github.pgreze.process.process
|
||||
import com.jaytux.phoebench.clients.cli.ProjectHandlers.toId
|
||||
import com.jaytux.phoebench.common.ANSI
|
||||
import com.jaytux.phoebench.common.ClientMonitorEvent
|
||||
import com.jaytux.phoebench.common.Routes
|
||||
import com.jaytux.phoebench.common.Stream
|
||||
import com.jaytux.phoebench.common.bind
|
||||
import com.jaytux.phoebench.common.fold
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.utils.io.CancellationException
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
object MonitorHandler {
|
||||
private fun filterAnsi(line: String): Pair<String, List<ANSI>> {
|
||||
val builder = StringBuilder()
|
||||
val codes = mutableSetOf<ANSI>()
|
||||
var remaining = line
|
||||
while(remaining.isNotEmpty()) {
|
||||
val next = remaining.indexOf("\u001B[")
|
||||
if(next < 0) {
|
||||
builder.append(remaining)
|
||||
remaining = ""
|
||||
}
|
||||
else {
|
||||
builder.append(remaining.substring(0, next))
|
||||
val end = remaining.indexOf('m', startIndex = next + 2)
|
||||
if(end == -1) return (line to listOf()) // invalid...
|
||||
codes += remaining.substring(next + 2, end).split(';').mapNotNull {
|
||||
it.toIntOrNull()?.let { i -> ANSI.ansiMapping[i] }
|
||||
}
|
||||
remaining = remaining.substring(end + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString() to codes.toList()
|
||||
}
|
||||
|
||||
fun monitor(find: CLI.Commands.Project.IProjectIdentification?, command: String, args: List<String>) {
|
||||
val project = ProjectHandlers.ensureProjectIdentification(find)
|
||||
val combinedCommand = arrayOf(command, *args.toTypedArray())
|
||||
tryAuthenticated {
|
||||
project.toId().bind { id ->
|
||||
Client.callCSE(Routes.CSE.monitor, id) { sender ->
|
||||
try {
|
||||
val res = process(
|
||||
*combinedCommand,
|
||||
stdin = null,
|
||||
stdout = Redirect.Consume { flow ->
|
||||
flow.collect { line ->
|
||||
val (updLine, options) = filterAnsi(line)
|
||||
sender(ClientMonitorEvent.Message(msg = updLine, stream = Stream.STDOUT, options = options))
|
||||
println(line)
|
||||
}
|
||||
},
|
||||
stderr = Redirect.Consume { flow ->
|
||||
flow.collect { line ->
|
||||
val (updLine, options) = filterAnsi(line)
|
||||
sender(ClientMonitorEvent.Message(msg = updLine, stream = Stream.STDERR, options = options))
|
||||
System.err.println(line)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
sender(ClientMonitorEvent.ApplicationFinished(res.resultCode))
|
||||
}
|
||||
catch(e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch(e: Exception) {
|
||||
System.err.println("Launch failed: ${e.message} (${e::class.simpleName})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.fold({
|
||||
System.err.println("Failed to run: ${it.msg}")
|
||||
}) {}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import kotlin.uuid.Uuid
|
||||
|
||||
object PersistentStorage {
|
||||
private val json = Json
|
||||
const val SERVICE = "com.jaytux.phoebench.cli"
|
||||
const val SERVICE = PersistenceConstants.SERVICE
|
||||
private var _backend: IBackend = KeyringBackend
|
||||
|
||||
interface IBackend {
|
||||
|
||||
Reference in New Issue
Block a user