A lot done here: Bump up to Micronaut 3.7.2 + first Zipkin integration + improved SWS integration + improved SSH service + etc.
Signed-off-by: Thraax Session <thraax.session@iatlas.technology>
This commit is contained in:
parent
cb9201170f
commit
0ce749634e
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gradle",
|
||||
"name": "Start Dev/Secured",
|
||||
"environment": {
|
||||
"MICRONAUT_ENVIRONMENTS": "dev"
|
||||
},
|
||||
"args": [
|
||||
"-Pmicronaut.server.cors.enabled='false'",
|
||||
"-Pmicronaut.security.enabled=true",
|
||||
"-Pmicronaut.server.host=0.0.0.0",
|
||||
"-Pspaceup.logging.level=DEBUG",
|
||||
"-Pspaceup.scheduler.domains.update=10m",
|
||||
"-Dmongodb.uri=mongodb://thraax_mongoroot:peijao3poongoof2sheewepahChoo3ic@mongo.iatlas.technology:41421/admin"
|
||||
],
|
||||
"tasks": [
|
||||
"run"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"toolchains": []
|
||||
}
|
|
@ -32,7 +32,7 @@ The server application is written in kotlin with micronaut as web framework. The
|
|||
|
||||
If any other feature is wished, or you want to *help me*, write me a small email.
|
||||
So I will register you for youtrack to open an issue, or we have a discussion how you can support the project:
|
||||
<spaceup-support@iatlas.technology>
|
||||
spaceup-support@iatlas.technology
|
||||
|
||||
Little big bonus: There is a multiplatform app (written in Flutter) which already consumes the APIs. 😁
|
||||
You should know it's not tested with ios/osx as I am not an apple user.
|
||||
|
|
24
build.gradle
24
build.gradle
|
@ -27,15 +27,15 @@ configurations {
|
|||
|
||||
dependencies {
|
||||
kapt(platform("io.micronaut:micronaut-bom:$micronautVersion"))
|
||||
kapt('io.micronaut:micronaut-inject-java:3.6.3')
|
||||
kapt('io.micronaut:micronaut-validation:3.6.3')
|
||||
kapt('io.micronaut:micronaut-inject-java:3.7.1')
|
||||
kapt('io.micronaut:micronaut-validation:3.7.1')
|
||||
kapt("io.micronaut:micronaut-graal:$micronautVersion")
|
||||
kapt('io.micronaut.openapi:micronaut-openapi:4.5.1')
|
||||
kapt('io.micronaut.openapi:micronaut-openapi:4.5.2')
|
||||
kapt('io.micronaut.security:micronaut-security-annotations:3.8.0')
|
||||
compileOnly(platform("io.micronaut:micronaut-bom:$micronautVersion"))
|
||||
compileOnly('org.graalvm.nativeimage:svm:22.2.0')
|
||||
implementation(platform("io.micronaut:micronaut-bom:$micronautVersion"))
|
||||
implementation('io.micronaut:micronaut-management:3.6.3')
|
||||
implementation('io.micronaut:micronaut-management:3.7.1')
|
||||
implementation("io.micronaut:micronaut-inject:$micronautVersion")
|
||||
implementation("io.micronaut:micronaut-validation:$micronautVersion")
|
||||
implementation('io.micronaut.rxjava3:micronaut-rxjava3:2.3.0')
|
||||
|
@ -48,9 +48,11 @@ dependencies {
|
|||
implementation("io.micronaut:micronaut-http-server-netty:$micronautVersion")
|
||||
implementation("io.micronaut:micronaut-http-client:$micronautVersion")
|
||||
implementation("io.micronaut.reactor:micronaut-reactor")
|
||||
implementation('io.swagger.core.v3:swagger-annotations:2.2.2')
|
||||
implementation('io.micronaut:micronaut-tracing:3.2.7')
|
||||
runtimeOnly('io.jaegertracing:jaeger-thrift:1.8.1')
|
||||
implementation('io.swagger.core.v3:swagger-annotations:2.2.4')
|
||||
implementation('io.micronaut:micronaut-tracing')
|
||||
implementation("io.opentracing.brave:brave-opentracing:1.0.0")
|
||||
runtimeOnly("io.zipkin.brave:brave-instrumentation-httpclient:5.14.1")
|
||||
runtimeOnly("io.zipkin.reporter:zipkin-reporter:1.1.2")
|
||||
implementation("io.micronaut.security:micronaut-security:3.2.0")
|
||||
implementation('io.micronaut.security:micronaut-security-jwt:3.8.0')
|
||||
implementation('io.micronaut.kotlin:micronaut-kotlin-extension-functions:3.2.2')
|
||||
|
@ -72,7 +74,7 @@ dependencies {
|
|||
implementation('org.jasypt:jasypt:1.9.3')
|
||||
|
||||
// My stuff
|
||||
implementation "technology.iatlas.sws:sws:1.3.2-SNAPSHOT"
|
||||
implementation("technology.iatlas.sws:sws:1.4.0-SNAPSHOT")
|
||||
|
||||
runtimeOnly("ch.qos.logback:logback-classic")
|
||||
runtimeOnly('com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1')
|
||||
|
@ -86,7 +88,7 @@ dependencies {
|
|||
testImplementation("io.micronaut.test:micronaut-test-junit5:$micronautVersion")
|
||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.9.0')
|
||||
// Docker Testcontainer
|
||||
testImplementation 'org.testcontainers:testcontainers:1.17.3'
|
||||
testImplementation 'org.testcontainers:testcontainers:1.17.4'
|
||||
}
|
||||
|
||||
test.classpath += configurations.developmentOnly
|
||||
|
@ -123,10 +125,10 @@ kapt {
|
|||
arg("micronaut.processing.incremental", true)
|
||||
arg("micronaut.processing.annotations", "technology.iatlas.spaceup.*")
|
||||
arg("micronaut.processing.group", "technology.iatlas.spaceup")
|
||||
arg("micronaut.processing.module", "spaceUp")
|
||||
arg("micronaut.processing.module", "SpaceUp")
|
||||
arg("micronaut.openapi.views.spec",
|
||||
"redoc.enabled=true,rapidoc.enabled=true," +
|
||||
"swagger-ui.enabled=true,swagger-ui.theme=flattop")
|
||||
"swagger-ui.enabled=true,swagger-ui.theme=material")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,6 @@
|
|||
# Thanks, and we hope you enjoy using SpaceUp-Server and that it's everything you ever hoped it could be.
|
||||
#
|
||||
|
||||
micronautVersion=3.7.1
|
||||
micronautVersion=3.7.2
|
||||
kotlinVersion=1.7.20
|
||||
kotlin.code.style=official
|
||||
|
|
|
@ -71,14 +71,6 @@ import java.text.Normalizer.Form
|
|||
import java.text.Normalizer as jNormalizer
|
||||
|
||||
|
||||
@OpenAPIDefinition(
|
||||
info = Info(
|
||||
title = "SpaceUp",
|
||||
version = "0.26.0",
|
||||
contact = Contact(name = "Thraax Session",
|
||||
url = "https://spaceup.iatlas.technology", email = "spaceup@iatlas.technology")
|
||||
)
|
||||
)
|
||||
@TypeHint(
|
||||
value = [
|
||||
LoggerFactory::class,
|
||||
|
@ -120,6 +112,14 @@ import java.text.Normalizer as jNormalizer
|
|||
"com.jcraft.jsch.UserAuthKeyboardInteractive",
|
||||
]
|
||||
)
|
||||
@OpenAPIDefinition(
|
||||
info = Info(
|
||||
title = "SpaceUp",
|
||||
version = "0.26.0",
|
||||
contact = Contact(name = "Thraax Session",
|
||||
url = "https://spaceup.iatlas.technology", email = "spaceup@iatlas.technology")
|
||||
)
|
||||
)
|
||||
object Api
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
|
|
@ -47,6 +47,7 @@ import io.micronaut.http.MediaType
|
|||
import io.micronaut.http.annotation.*
|
||||
import io.micronaut.security.annotation.Secured
|
||||
import io.micronaut.security.rules.SecurityRule
|
||||
import io.micronaut.tracing.annotation.ContinueSpan
|
||||
import org.slf4j.LoggerFactory
|
||||
import technology.iatlas.spaceup.core.annotations.Installed
|
||||
import technology.iatlas.spaceup.dto.Feedback
|
||||
|
@ -73,6 +74,7 @@ class SwsController(
|
|||
}
|
||||
|
||||
@Get(uri = "/all", produces = [MediaType.APPLICATION_JSON])
|
||||
@ContinueSpan
|
||||
suspend fun getAllSws(): HttpResponse<List<Sws>> {
|
||||
log.info("Retrieve all sws configurations")
|
||||
val allSws = swsService.getAll()
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
package technology.iatlas.spaceup.core.httpfilter
|
||||
|
||||
import brave.Tracing
|
||||
import io.micronaut.http.HttpRequest
|
||||
import io.micronaut.http.HttpStatus
|
||||
import io.micronaut.http.MutableHttpResponse
|
||||
|
@ -51,6 +52,7 @@ import io.micronaut.http.filter.ServerFilterChain
|
|||
import io.micronaut.http.simple.SimpleHttpResponseFactory
|
||||
import io.micronaut.security.annotation.Secured
|
||||
import io.micronaut.security.rules.SecurityRule
|
||||
import io.micronaut.tracing.annotation.NewSpan
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.reactivestreams.Publisher
|
||||
import reactor.core.publisher.Flux
|
||||
|
@ -61,21 +63,25 @@ import technology.iatlas.spaceup.services.SpaceUpService
|
|||
import technology.iatlas.spaceup.services.SwsService
|
||||
|
||||
|
||||
@Filter("/api/sws/custom/**")
|
||||
@Filter("/api/sws/execution/**")
|
||||
@Secured(SecurityRule.IS_AUTHENTICATED)
|
||||
class SwsRouteFilter(
|
||||
open class SwsRouteFilter(
|
||||
private val swsService: SwsService,
|
||||
private val dbService: DbService,
|
||||
private val spaceUpService: SpaceUpService
|
||||
private val spaceUpService: SpaceUpService,
|
||||
private val tracing: Tracing
|
||||
): HttpServerFilter {
|
||||
|
||||
override fun doFilter(request: HttpRequest<*>, chain: ServerFilterChain?): Publisher<MutableHttpResponse<*>> {
|
||||
@NewSpan("sws-http")
|
||||
override fun doFilter(
|
||||
request: HttpRequest<*>, chain: ServerFilterChain?): Publisher<MutableHttpResponse<*>> {
|
||||
return Flux.from(Mono.fromCallable {
|
||||
runBlocking {
|
||||
if(dbService.isAppInstalled() && (request.userPrincipal.isPresent || spaceUpService.isDevMode())) {
|
||||
if(dbService.isAppInstalled() &&
|
||||
((request.userPrincipal.isPresent && !spaceUpService.isDevMode()) || spaceUpService.isDevMode())) {
|
||||
swsService.execute(request)
|
||||
} else {
|
||||
SimpleHttpResponseFactory.INSTANCE.status(
|
||||
SimpleHttpResponseFactory.INSTANCE.status<String>(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR, "Server is not installed or user is not logged in.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022 spaceup@iatlas.technology.
|
||||
* Copyright (c) 2022 Thraax Session <spaceup@iatlas.technology>.
|
||||
*
|
||||
* SpaceUp-Server is free software; You can redistribute it and/or modify it under the terms of:
|
||||
* - the GNU Affero General Public License version 3 as published by the Free Software Foundation.
|
||||
* You don't have to do anything special to accept the license and you don’t have to notify anyone which that you have made that decision.
|
||||
|
@ -11,7 +12,6 @@
|
|||
* You should have received a copy of both licenses along with SpaceUp-Server
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* There is a strong belief within us that the license we have chosen provides not only the best solution for providing you with the essential freedom necessary to use SpaceUp-Server within your projects, but also for maintaining enough copyleft strength for us to feel confident and secure with releasing our hard work to the public. For your convenience we've included our own interpretation of the license we chose, which can be seen below.
|
||||
*
|
||||
* Our interpretation of the GNU Affero General Public License version 3: (Quoted words are words in which there exists a definition within the license to avoid ambiguity.)
|
||||
|
@ -62,8 +62,8 @@ class InstalledInterceptor(
|
|||
val db = dbService.getDb()
|
||||
val serverRepo = db.getCollection<Server>()
|
||||
|
||||
val server = serverRepo.find().first()!!
|
||||
if(!server.installed) {
|
||||
val server: Server? = serverRepo.find().first()
|
||||
if(server == null || !server.installed) {
|
||||
throw NotInstalledException("Application needs to be proper installed!")
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ import technology.iatlas.spaceup.services.InstallerService
|
|||
import technology.iatlas.spaceup.services.SpaceUpService
|
||||
import technology.iatlas.spaceup.services.SshService
|
||||
import technology.iatlas.spaceup.services.SwsService
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
|
@ -92,18 +93,20 @@ class StartupEventListener(
|
|||
}
|
||||
}
|
||||
|
||||
// Step 1: create directories if not exist
|
||||
createDirectories()
|
||||
runBlocking {
|
||||
createExternalDirectories()
|
||||
// Step 1: check if spaceup was installed
|
||||
val isInstalled = checkInstallation()
|
||||
|
||||
if(isInstalled) {
|
||||
// Step 2: create directories if not exist
|
||||
createDirectories()
|
||||
runBlocking {
|
||||
createExternalDirectories()
|
||||
}
|
||||
|
||||
// Step 3: fill sws cache
|
||||
fillSwsCache()
|
||||
}
|
||||
|
||||
// Step 2: check if spaceup was installed
|
||||
checkInstallation()
|
||||
|
||||
// Step 3: fill sws cache
|
||||
fillSwsCache()
|
||||
|
||||
log.info("Finished SpaceUp startup")
|
||||
}
|
||||
|
||||
|
@ -150,12 +153,13 @@ class StartupEventListener(
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkInstallation() {
|
||||
private fun checkInstallation(): Boolean {
|
||||
// Let's check if we are already installed properly
|
||||
val db = dbService.getDb()
|
||||
val serverRepo = db.getCollection<Server>()
|
||||
val server = serverRepo.find().firstOrNull()
|
||||
|
||||
var isInstalled = false
|
||||
if(server == null) {
|
||||
log.info("Seems to be first run. Set not installed!")
|
||||
val apiKey = installerService.generateAPIKey()
|
||||
|
@ -171,7 +175,10 @@ class StartupEventListener(
|
|||
log.info("Finish installation with API key: ${server.apiKey.yellow.bold}")
|
||||
}
|
||||
}
|
||||
isInstalled = installed
|
||||
}
|
||||
|
||||
return isInstalled
|
||||
}
|
||||
|
||||
private fun fillSwsCache() {
|
||||
|
@ -198,4 +205,8 @@ class StartupEventListener(
|
|||
|
||||
fun String.createNormalizedPath(): Path {
|
||||
return Path(this).normalize()
|
||||
}
|
||||
|
||||
fun String.toFile(): File {
|
||||
return this.createNormalizedPath().toFile()
|
||||
}
|
|
@ -50,9 +50,9 @@ import com.jcraft.jsch.JSchException
|
|||
import com.jcraft.jsch.Session
|
||||
import io.micronaut.context.annotation.Context
|
||||
import io.micronaut.context.annotation.Value
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import io.micronaut.tracing.annotation.NewSpan
|
||||
import io.micronaut.tracing.annotation.SpanTag
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.litote.kmongo.findOne
|
||||
import org.litote.kmongo.getCollection
|
||||
import org.slf4j.LoggerFactory
|
||||
|
@ -83,7 +83,7 @@ class SshService(
|
|||
private lateinit var useDbCredentials: String
|
||||
|
||||
// Configure SSH
|
||||
fun initSSH() {
|
||||
suspend fun initSSH() {
|
||||
val jsch = JSch()
|
||||
|
||||
var username = ""
|
||||
|
@ -146,13 +146,14 @@ class SshService(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun execute(command: CommandInf): SshResponse {
|
||||
@NewSpan
|
||||
suspend fun execute(@SpanTag("ssh-command")command: CommandInf): SshResponse {
|
||||
log.trace("Execute $command")
|
||||
if(!this::session.isInitialized || !session.isConnected) {
|
||||
initSSH()
|
||||
}
|
||||
|
||||
val channel: ChannelExec = try {
|
||||
val executionChannel: ChannelExec = try {
|
||||
session.openChannel("exec") as ChannelExec
|
||||
} catch (shhEx: JSchException) {
|
||||
log.error("SSH Session is down. Will try to reconnect.")
|
||||
|
@ -161,31 +162,33 @@ class SshService(
|
|||
}
|
||||
|
||||
try {
|
||||
channel.setCommand(command.parameters.joinToString(" "))
|
||||
executionChannel.setCommand(command.parameters.joinToString(" "))
|
||||
val responseStream = ByteArrayOutputStream()
|
||||
val errorResponseStream = ByteArrayOutputStream()
|
||||
channel.outputStream = responseStream
|
||||
channel.setErrStream(errorResponseStream)
|
||||
executionChannel.outputStream = responseStream
|
||||
executionChannel.setErrStream(errorResponseStream)
|
||||
|
||||
try {
|
||||
channel.connect()
|
||||
executionChannel.connect()
|
||||
} catch (shhEx: JSchException) {
|
||||
log.error("SSH Session is down. Will try to reconnect.")
|
||||
initSSH()
|
||||
}
|
||||
|
||||
// When then channel close itself, we retrieved the data
|
||||
while (channel.isConnected) {
|
||||
delay(50)
|
||||
while (executionChannel.isConnected) {
|
||||
delay(20)
|
||||
}
|
||||
|
||||
val stdout = String(responseStream.toByteArray())
|
||||
var stderr = String(errorResponseStream.toByteArray())
|
||||
if(stderr.isNotEmpty() && channel.exitStatus != 0) {
|
||||
if (stderr.isNotEmpty() && executionChannel.exitStatus != 0) {
|
||||
stderr = """
|
||||
Command: ${command.parameters.joinToString { " " }}
|
||||
terminated with exit code: ${channel.exitStatus}
|
||||
""".trimIndent()
|
||||
Script: ${command.shellScript}
|
||||
Command: ${command.parameters.joinToString(" ")}
|
||||
Script error: $stderr
|
||||
terminated with exit code: ${executionChannel.exitStatus}
|
||||
""".trimIndent()
|
||||
colored {
|
||||
log.error(stderr.red)
|
||||
}
|
||||
|
@ -196,7 +199,7 @@ class SshService(
|
|||
return sshResponse
|
||||
} finally {
|
||||
//session.disconnect()
|
||||
channel.disconnect()
|
||||
executionChannel.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +207,8 @@ class SshService(
|
|||
* Upload a shell script via SFTP and execute it optionally
|
||||
*
|
||||
*/
|
||||
suspend fun upload(cmd: CommandInf): SshResponse {
|
||||
@NewSpan
|
||||
suspend fun upload(@SpanTag("ssh-command") cmd: CommandInf): SshResponse {
|
||||
log.debug("Upload $cmd")
|
||||
if(!session.isConnected) {
|
||||
initSSH()
|
||||
|
@ -219,67 +223,31 @@ class SshService(
|
|||
sftpConfig.remotedir?.replace("~", "/home/${ssh.username}") + "/${file.name}"
|
||||
|
||||
val sftpChannel: Channel = session.openChannel("sftp") as Channel
|
||||
|
||||
val executionChannel: ChannelExec = session.openChannel("exec") as ChannelExec
|
||||
val executeCmd = cmd.parameters.joinToString(" ")
|
||||
executionChannel.setCommand(executeCmd)
|
||||
|
||||
val responseExecution = ByteArrayOutputStream()
|
||||
val errorExecution = ByteArrayOutputStream()
|
||||
executionChannel.outputStream = responseExecution
|
||||
executionChannel.setErrStream(errorExecution)
|
||||
|
||||
var sshResponse = SshResponse("", "")
|
||||
try {
|
||||
sftpChannel.connect()
|
||||
if(!sftpChannel.isConnected) {
|
||||
sftpChannel.connect()
|
||||
}
|
||||
|
||||
val sftp = sftpChannel as ChannelSftp
|
||||
log.debug("Upload script ${file.name} to $remotefile")
|
||||
withContext(Dispatchers.IO) {
|
||||
val os = System.getProperty("os.name")
|
||||
|
||||
if(os.lowercase().contains("windows")) {
|
||||
val localScript = file.scriptPath?.file ?: ""
|
||||
sftp.put(File(localScript).canonicalPath, remotefile, ChannelSftp.OVERWRITE)
|
||||
} else {
|
||||
sftp.put(file.scriptPath?.openStream(), remotefile, ChannelSftp.OVERWRITE)
|
||||
}
|
||||
val os = System.getProperty("os.name")
|
||||
if(os.lowercase().contains("windows")) {
|
||||
val localScript = file.scriptPath?.file ?: ""
|
||||
sftp.put(File(localScript).canonicalPath, remotefile, ChannelSftp.OVERWRITE)
|
||||
} else {
|
||||
sftp.put(file.scriptPath?.file, remotefile, ChannelSftp.OVERWRITE)
|
||||
}
|
||||
|
||||
if(file.execute) {
|
||||
log.trace("""
|
||||
"Execute script: ${cmd.parameters}
|
||||
$executeCmd
|
||||
""".trimIndent())
|
||||
executionChannel.connect()
|
||||
|
||||
// When then channel close itself, we retrieved the data
|
||||
while (executionChannel.isConnected) {
|
||||
delay(50)
|
||||
}
|
||||
|
||||
val stdout = String(responseExecution.toByteArray())
|
||||
var stderr = String(errorExecution.toByteArray())
|
||||
if(stderr.isNotEmpty() && executionChannel.exitStatus != 0) {
|
||||
stderr = """
|
||||
Script: ${cmd.shellScript}
|
||||
Command: ${cmd.parameters.joinToString(" ")}
|
||||
Script error: $stderr
|
||||
terminated with exit code: ${executionChannel.exitStatus}
|
||||
""".trimIndent()
|
||||
colored {
|
||||
log.error(stderr.red)
|
||||
}
|
||||
}
|
||||
sshResponse = SshResponse(stdout, stderr)
|
||||
// SshResponse
|
||||
log.trace(sshResponse.toString())
|
||||
sshResponse = execute(cmd)
|
||||
}
|
||||
|
||||
log.trace(sshResponse.toString())
|
||||
return sshResponse
|
||||
} finally {
|
||||
sftpChannel.disconnect()
|
||||
executionChannel.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,11 +43,13 @@
|
|||
package technology.iatlas.spaceup.services
|
||||
|
||||
import com.mongodb.client.MongoCollection
|
||||
import io.micronaut.context.annotation.Context
|
||||
import io.micronaut.http.HttpRequest
|
||||
import io.micronaut.http.HttpStatus
|
||||
import io.micronaut.http.MutableHttpResponse
|
||||
import io.micronaut.http.simple.SimpleHttpResponseFactory
|
||||
import io.micronaut.tracing.annotation.NewSpan
|
||||
import io.micronaut.tracing.annotation.SpanTag
|
||||
import jakarta.inject.Singleton
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.litote.kmongo.eq
|
||||
|
@ -56,7 +58,7 @@ import technology.iatlas.spaceup.config.SpaceUpSftpConfig
|
|||
import technology.iatlas.spaceup.config.SpaceupPathConfig
|
||||
import technology.iatlas.spaceup.core.cmd.SshResponse
|
||||
import technology.iatlas.spaceup.core.cmd.toFeedback
|
||||
import technology.iatlas.spaceup.core.startup.createNormalizedPath
|
||||
import technology.iatlas.spaceup.core.startup.toFile
|
||||
import technology.iatlas.spaceup.dto.Command
|
||||
import technology.iatlas.spaceup.dto.Feedback
|
||||
import technology.iatlas.spaceup.dto.SftpFile
|
||||
|
@ -66,8 +68,8 @@ import technology.iatlas.sws.SWS
|
|||
import technology.iatlas.sws.objects.ParserException
|
||||
import java.io.File
|
||||
|
||||
@Context
|
||||
class SwsService(
|
||||
@Singleton
|
||||
open class SwsService(
|
||||
private val dbService: DbService,
|
||||
private val sshService: SshService,
|
||||
private val sftpConfig: SpaceUpSftpConfig,
|
||||
|
@ -81,7 +83,7 @@ class SwsService(
|
|||
private fun validateSWS(sws: Sws, feedback: Feedback) {
|
||||
log.info("Validate sws")
|
||||
// Create a temporary file
|
||||
"$tempDir/${sws.name}.sws".createNormalizedPath().toFile().apply {
|
||||
"$tempDir/${sws.name}.sws".toFile().apply {
|
||||
this.writeText(sws.content)
|
||||
try {
|
||||
SWS.createAndParse(this)
|
||||
|
@ -198,11 +200,12 @@ class SwsService(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun execute(request: HttpRequest<*>): MutableHttpResponse<Feedback> {
|
||||
val path = request.path.split("/api/sws/custom")[1]
|
||||
@NewSpan("sws-execute")
|
||||
open suspend fun execute(@SpanTag("http-request") request: HttpRequest<*>): MutableHttpResponse<Feedback> {
|
||||
val path = request.path.split("/api/sws/execution")[1]
|
||||
val httpMethod = request.method
|
||||
val parameters: Map<String, List<String>> = request.parameters.asMap()
|
||||
val bodyOptional = request.body
|
||||
val httpBody = request.body
|
||||
|
||||
log.info("Execute SWS [$httpMethod] [$path] ${parameters.map {
|
||||
if(it.key.lowercase() == "pass" || it.key.lowercase() == "password")
|
||||
|
@ -212,25 +215,25 @@ class SwsService(
|
|||
|
||||
// Handle HTTP request parameters
|
||||
val swsHttpParameters = mutableMapOf<String, Any?>()
|
||||
parameters.forEach { (t, u) ->
|
||||
parameters.forEach { (k, v) ->
|
||||
try {
|
||||
// Values come always as String. We have to cast numbers to integers
|
||||
swsHttpParameters[t] = Integer.valueOf(u[0])
|
||||
swsHttpParameters[k] = Integer.valueOf(v[0])
|
||||
} catch (ex: NumberFormatException) {
|
||||
swsHttpParameters[t] = u[0]
|
||||
swsHttpParameters[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SU-19 Handle request body, important to transport secrets/credentials
|
||||
val body = mutableMapOf<String, Any?>()
|
||||
if(bodyOptional.isPresent) {
|
||||
if(httpBody.isPresent) {
|
||||
// The body is a map of anything
|
||||
val tempBody = bodyOptional.get() as Map<*, *>
|
||||
val tempBody = httpBody.get() as Map<*, *>
|
||||
tempBody.forEach { (k, v) ->
|
||||
if(k is String) {
|
||||
if(k is String) { // the key needs always be a string for mapping
|
||||
body[k] = v
|
||||
} else {
|
||||
log.warn("Key $k is not a string. Will be ignored!")
|
||||
log.warn("Key $k is not a string. '$k' and '$v' will be ignored!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,7 +257,7 @@ class SwsService(
|
|||
|
||||
// Generate SWS
|
||||
var sws: SWS
|
||||
val file = "$tempDir/${swsDb.name}.sws".createNormalizedPath().toFile()
|
||||
val file = "$tempDir/${swsDb.name}.sws".toFile()
|
||||
file.bufferedWriter().use {
|
||||
it.write(swsDb.content)
|
||||
}.apply {
|
||||
|
@ -272,7 +275,7 @@ class SwsService(
|
|||
// Actual execution
|
||||
var response: SshResponse
|
||||
val scriptname = "${sws.name.replace(" ", "_")}.sh"
|
||||
"$tempDir/$scriptname".createNormalizedPath().toFile().apply {
|
||||
"$tempDir/$scriptname".toFile().apply {
|
||||
this.writeText(sws.serverScript)
|
||||
|
||||
val script = "${sftpConfig.remotedir}/$scriptname"
|
||||
|
|
|
@ -83,20 +83,33 @@ micronaut.server.ssl.build-self-signed=true
|
|||
micronaut.server.http-version=1.1
|
||||
micronaut.server.ssl.port=9094
|
||||
|
||||
# Tracing
|
||||
tracing.zipkin.enabled=false
|
||||
tracing.zipkin.http.url=http://localhost:9411
|
||||
tracing.zipkin.sampler.probability=1
|
||||
|
||||
micronaut.caches.my-cache.maximumSize=100
|
||||
|
||||
# Hotreload for views
|
||||
micronaut.views.rocker.hot-reloading=true
|
||||
|
||||
micronaut.openapi.property.naming.strategy=KEBAB_CASE
|
||||
micronaut.openapi.target.file=myspecfile.yml
|
||||
micronaut.openapi.target.file=spaceup-openapi.yml
|
||||
swagger.enabled=true
|
||||
swagger-ui.enabled=true
|
||||
redoc.enabled=true
|
||||
|
||||
# Defaults
|
||||
#micronaut.router.static-resources.default.mapping=/**
|
||||
#micronaut.router.static-resources.default.enabled=true
|
||||
#micronaut.router.static-resources.default.paths=classpath:static
|
||||
spaceup.home=${user.home}/.spaceup
|
||||
|
||||
# Web client
|
||||
# Will be used to download the client there
|
||||
spaceup.ui.dir=${spaceup.home}/static/ui
|
||||
spaceup.ui.enabled=false
|
||||
|
||||
micronaut.router.static-resources.default.mapping=/ui/**
|
||||
micronaut.router.static-resources.default.enabled=${spaceup.ui.enabled}
|
||||
micronaut.router.static-resources.default.paths=file:${spaceup.ui.dir}
|
||||
|
||||
# Swagger
|
||||
micronaut.router.static-resources.swagger.paths=classpath:META-INF/swagger
|
||||
|
@ -122,6 +135,7 @@ devurl.Redoc.enabled=true
|
|||
devurl.Redoc.mapping=/redoc
|
||||
devurl.Redoc.newtab=true
|
||||
|
||||
# Update this to spaceup.remote.path...
|
||||
spaceup.path.services=~/etc/services.d/
|
||||
spaceup.path.logs=~/logs/
|
||||
spaceup.path.temp=~/.spaceup/tmp
|
||||
|
@ -133,6 +147,7 @@ spaceup.path.temp=~/.spaceup/tmp
|
|||
#spaceup.ssh.password
|
||||
#spaceup.ssh.host
|
||||
spaceup.ssh.port=22
|
||||
# Update this to spaceup.sftp.remote.temp
|
||||
spaceup.sftp.remotedir=~/.spaceup/tmp
|
||||
# If set, SpaceUp will take the credentials from db.
|
||||
# # Otherwise you have to supply -spaceup.ssh.* configuration.
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
rapidoc.requestUpdate();
|
||||
});
|
||||
}
|
||||
rapidoc.setAttribute('spec-url', contextPath + '/swagger/myspecfile.yml');
|
||||
rapidoc.setAttribute('spec-url', contextPath + '/swagger/spaceup-openapi.yml');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
},
|
||||
cookie = extract(document.cookie),
|
||||
contextPath = cookie === '' ? extract(window.location.search.substring(1)) : cookie;
|
||||
Redoc.init(contextPath + '/swagger/myspecfile.yml');
|
||||
Redoc.init(contextPath + '/swagger/spaceup-openapi.yml');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<script src='res/swagger-ui-bundle.js'></script>
|
||||
<script src='res/swagger-ui-standalone-preset.js'></script>
|
||||
<link rel='stylesheet' type='text/css' href='res/swagger-ui.css' />
|
||||
<link rel='stylesheet' type='text/css' href='res/flattop.css' />
|
||||
<link rel='stylesheet' type='text/css' href='res/material.css' />
|
||||
|
||||
</head>
|
||||
|
||||
|
@ -85,7 +85,7 @@
|
|||
};
|
||||
},
|
||||
ui = SwaggerUIBundle({
|
||||
url: contextPath + '/swagger/myspecfile.yml',
|
||||
url: contextPath + '/swagger/spaceup-openapi.yml',
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue