major moving around, rework of script triggering and running

master
Josha von Gizycki 1 day ago
parent 5345da6232
commit 992a5a922d

@ -4,7 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class WebApplication
open class WebApplication
fun main(args: Array<String>) {
runApplication<WebApplication>(*args)

@ -1,5 +1,6 @@
package alfred.web.core
import alfred.web.core.build.BuildId
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import java.time.Instant

@ -1,11 +1,13 @@
package alfred.web.core
import alfred.web.core.build.BuildConfig
import alfred.web.core.build.LogFile
import org.springframework.stereotype.Service
@Service
class Processes {
fun builder(config: BuildConfig, logFile: LogFile, rev: String): ProcessBuilder =
fun builder(config: BuildConfig, logFile: LogFile, rev: String?): ProcessBuilder =
ProcessBuilder()
.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile.backingFile))
.redirectError(ProcessBuilder.Redirect.appendTo(logFile.backingFile))

@ -1,46 +0,0 @@
package alfred.web.core
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.io.File
import java.nio.file.Paths
@Service
class ScriptRunner(
val builds: Builds,
val handles: Handles,
val processes: Processes
) {
fun run(buildId: BuildId, rev: String): ProcessInfo {
val config = builds.buildConfig(buildId)
val logFile = builds.createLogFile(buildId)
logger.info("preparing process for build $buildId with config $config")
logger.info("log file: $logFile")
logFile.header(buildId, rev, Paths.get(config.gitWorkspace))
val process = processes.builder(config, logFile, rev)
.command(config.script)
.start()
handles.add(Handle(process.toHandle(), buildId))
process.onExit().whenComplete { _, _ -> logFile.footer() }
val pid = process.pid()
logger.info("pid for build $buildId is: $pid")
return ProcessInfo(pid = pid, logFile = logFile)
}
val logger: Logger = LoggerFactory.getLogger(this::class.java)
}
data class ProcessInfo(
val pid: Long,
val logFile: LogFile
)

@ -0,0 +1,8 @@
package alfred.web.core.build
data class BuildContext(
val config: BuildConfig,
val logFile: LogFile,
val rev: String,
val buildId: BuildId
)

@ -1,4 +1,4 @@
package alfred.web.core
package alfred.web.core.build
import com.fasterxml.jackson.annotation.JsonIgnore
import org.slf4j.Logger
@ -8,11 +8,17 @@ import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ResponseStatus
import java.lang.RuntimeException
import java.nio.file.Files
import java.nio.file.Files.createFile
import java.nio.file.Paths
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import kotlin.collections.associate
import kotlin.collections.filter
import kotlin.io.inputStream
import kotlin.jvm.java
import kotlin.text.toLong
import kotlin.text.uppercase
@Service
class Builds(
@ -42,15 +48,7 @@ class Builds(
.associate { Pair(it.key.toString(), it.value.toString()) }
// diverge into ScriptedBasedConfig and GitBasedConfig
return BuildConfig(
user = props.getProperty("user"),
gitWorkspace = props.getProperty("workspace") ?: "",
apikey = props.getProperty("apikey") ?: "",
script = props.getProperty("script"),
gitRepo = props.getProperty("git.repo.url"),
gitCloneTimeout = props.getProperty("git.close.timeout")?.toLong() ?: 30L,
env = env
)
return props.toBuildConfig(env)
}
fun createLogFile(build: BuildId): LogFile {
@ -60,11 +58,22 @@ class Builds(
val path = Paths.get(home, "logs", fileName)
path.toFile().parentFile.mkdirs()
return LogFile(Files.createFile(path).toFile())
return LogFile(createFile(path).toFile())
}
}
private fun Properties.toBuildConfig(env: Map<String, String>) =
BuildConfig(
user = getProperty("user"),
workspace = getProperty("workspace"),
apikey = getProperty("apikey") ?: "",
script = getProperty("script"),
gitRepo = getProperty("git.repo.url"),
gitCloneTimeout = getProperty("git.close.timeout")?.toLong() ?: 30L,
env = env
)
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Unknown Build Id")
class UnknownBuild(val build: BuildId) : RuntimeException() {
override fun toString() =
@ -78,7 +87,7 @@ data class BuildConfig(
val script: String?,
val gitRepo: String?,
val gitCloneTimeout: Long,
val gitWorkspace: String,
val workspace: String,
@field:JsonIgnore
val env: Map<String, String>
)

@ -1,18 +1,22 @@
package alfred.web.core
package alfred.web.core.build
import java.io.File
import java.nio.file.Path
import java.time.ZonedDateTime
import kotlin.collections.joinToString
import kotlin.io.appendText
import kotlin.text.isEmpty
import kotlin.text.lines
import kotlin.text.trim
class LogFile(
val backingFile: File
) {
fun header(buildId: BuildId, rev: String, workspace: Path) {
fun header(buildId: BuildId, rev: String?, workspace: Path) {
append("At your service.")
append("Build $buildId, rev [$rev] started at ${ZonedDateTime.now()}")
append("Workspace directory is: $workspace\n")
append("Build $buildId, rev [${rev ?: "-none-"}] started at ${ZonedDateTime.now()}")
append("Workspace directory is: ${workspace.toFile().absolutePath}")
}
fun footer() {
@ -30,4 +34,4 @@ class LogFile(
} + "\n"
)
}
}

@ -0,0 +1,6 @@
package alfred.web.core.build
data class ProcessInfo(
val pid: Long,
val logFile: LogFile
)

@ -1,22 +1,31 @@
package alfred.web.core
package alfred.web.core.build
import org.springframework.stereotype.Service
import java.nio.file.Path
import java.nio.file.Paths
import java.util.UUID
import kotlin.collections.isNotEmpty
import kotlin.io.deleteRecursively
@Service
class Workspaces {
fun withWorkspace(ctx: BuildContext, block: (Path) -> Unit) {
val wsDir = prepare(ctx)
try {
block(wsDir)
} finally {
wsDir.toFile().deleteRecursively()
}
}
fun prepare(ctx: BuildContext): Path {
val workspacePath = Paths.get(
ctx.config.gitWorkspace, UUID.randomUUID().toString()
ctx.config.workspace, UUID.randomUUID().toString()
)
val workspaceDir = workspacePath.toFile()
ctx.logFile.header(ctx.buildId, ctx.rev, workspacePath)
if (!workspaceDir.exists()) {
ctx.logFile.append("creating workspace ${workspaceDir.absolutePath}")
workspaceDir.mkdirs()
@ -28,12 +37,11 @@ class Workspaces {
throw WorkspaceIsNotEmpty(workspaceDir.toString())
}
try {
block(workspaceDir.toPath())
} finally {
ctx.logFile.footer()
workspaceDir.deleteRecursively()
}
return workspacePath
}
fun cleanUp(wsDir: Path) {
wsDir.toFile().deleteRecursively()
}
}

@ -1,10 +1,20 @@
package alfred.web.core
package alfred.web.core.runner
import alfred.web.core.Handle
import alfred.web.core.Handles
import alfred.web.core.Processes
import alfred.web.core.build.BuildContext
import alfred.web.core.build.BuildId
import alfred.web.core.build.Builds
import alfred.web.core.build.ProcessInfo
import alfred.web.core.build.Workspaces
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.nio.file.Path
import java.util.concurrent.TimeUnit
import kotlin.collections.forEach
import kotlin.jvm.java
@Service
class GitRunner(
@ -32,8 +42,12 @@ class GitRunner(
Thread {
workspaces.withWorkspace(ctx) { wsPath ->
logFile.header(buildId, rev, wsPath)
clone(ctx, wsPath)
execScripts(ctx, wsPath)
logFile.footer()
}
}.start()
@ -97,12 +111,6 @@ class GitRunner(
}
data class BuildContext(
val config: BuildConfig,
val logFile: LogFile,
val rev: String,
val buildId: BuildId
)
class FailedToClone(
private val buildId: BuildId,

@ -0,0 +1,61 @@
package alfred.web.core.runner
import alfred.web.core.Handle
import alfred.web.core.Handles
import alfred.web.core.Processes
import alfred.web.core.build.BuildContext
import alfred.web.core.build.BuildId
import alfred.web.core.build.Builds
import alfred.web.core.build.ProcessInfo
import alfred.web.core.build.Workspaces
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import kotlin.jvm.java
@Service
class ScriptRunner(
val builds: Builds,
val handles: Handles,
val processes: Processes,
val workspaces: Workspaces
) {
fun run(buildId: BuildId, rev: String?): ProcessInfo {
val config = builds.buildConfig(buildId)
val logFile = builds.createLogFile(buildId)
logger.info("preparing process for build $buildId with config $config")
logger.info("log file: ${logFile.backingFile.absolutePath}")
val ctx = BuildContext(
config = config,
logFile = logFile,
rev = rev ?: "",
buildId = buildId
)
val wsDir = workspaces.prepare(ctx)
logFile.header(buildId, rev, wsDir)
val process = processes.builder(config, logFile, rev)
.command(config.script)
.directory(wsDir.toFile())
.start()
handles.add(Handle(process.toHandle(), buildId))
process.onExit().whenComplete { _, _ ->
logFile.footer()
workspaces.cleanUp(wsDir)
}
val pid = process.pid()
logger.info("pid for build $buildId is: $pid")
return ProcessInfo(pid = pid, logFile = logFile)
}
val logger: Logger = LoggerFactory.getLogger(this::class.java)
}

@ -0,0 +1,18 @@
package alfred.web.cron
import alfred.web.core.build.Builds
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit
@Service
class CronTrigger(
private val builds: Builds
) {
@Scheduled(fixedDelay = 1L, timeUnit= TimeUnit.MINUTES)
fun checkTrigger() {
}
}

@ -0,0 +1,45 @@
package alfred.web.http
import alfred.web.core.Handles
import alfred.web.core.build.BuildId
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("build")
class BuildsInfo(
val security: Security,
val handles: Handles
) {
@GetMapping(
"{build}/info",
produces = [MediaType.APPLICATION_JSON_VALUE]
)
fun info(
@PathVariable("build")
build: BuildId,
@RequestParam("key")
key: String?
) = security.requireKey(build, key) {
it
}
@GetMapping(
"{build}/handles",
produces = [MediaType.APPLICATION_JSON_VALUE]
)
fun handles(
@PathVariable("build")
build: BuildId,
@RequestParam("key")
key: String?
) = security.requireKey(build, key) {
handles.active(build)
}
}

@ -1,47 +1,33 @@
package alfred.web.http
import alfred.web.core.BuildId
import alfred.web.core.Builds
import alfred.web.core.GitRunner
import alfred.web.core.Handles
import alfred.web.core.ScriptRunner
import alfred.web.core.build.BuildId
import alfred.web.core.build.Builds
import alfred.web.core.runner.GitRunner
import alfred.web.core.runner.ScriptRunner
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("build")
class Endpoints(
class HttpTrigger(
val builds: Builds,
val security: Security,
val scriptRunner: ScriptRunner,
val gitRunner: GitRunner,
val handles: Handles
) {
@GetMapping(
"{build}/info",
produces = [MediaType.APPLICATION_JSON_VALUE]
)
fun info(
@PathVariable("build")
build: BuildId,
@RequestParam("key")
key: String?
) = security.requireKey(build, key) {
it
}
@PostMapping(
"{build}/trigger",
"{build}/trigger/git",
produces = [MediaType.APPLICATION_JSON_VALUE]
)
fun trigger(
fun triggerGit(
@PathVariable("build")
build: BuildId,
@RequestParam("key")
@ -49,12 +35,13 @@ class Endpoints(
@RequestParam("rev")
rev: String
) = security.requireKey(build, key) {
val info = if (builds.buildConfig(build).script != null) {
scriptRunner.run(build, rev)
} else {
gitRunner.run(build, rev)
val config = builds.buildConfig(build)
if (config.gitRepo == null) {
throw UnsupportedMode()
}
val info = gitRunner.run(build, rev)
ResponseEntity.ok(
mapOf(
"log" to info.logFile.backingFile.absolutePath,
@ -63,18 +50,35 @@ class Endpoints(
)
}
@GetMapping(
"{build}/handles",
@PostMapping(
"{build}/trigger/script",
produces = [MediaType.APPLICATION_JSON_VALUE]
)
fun handles(
fun triggerScript(
@PathVariable("build")
build: BuildId,
@RequestParam("key")
key: String?
key: String?,
@RequestParam("rev")
rev: String?
) = security.requireKey(build, key) {
handles.active(build)
val config = builds.buildConfig(build)
if (config.script == null) {
throw UnsupportedMode()
}
val info = scriptRunner.run(build, rev)
ResponseEntity.ok(
mapOf(
"log" to info.logFile.backingFile.absolutePath,
"pid" to info.pid
)
)
}
@ResponseStatus(value = HttpStatus.PRECONDITION_FAILED, reason = "build mode is not configured")
class UnsupportedMode : Exception()
}

@ -1,8 +1,8 @@
package alfred.web.http
import alfred.web.core.BuildConfig
import alfred.web.core.BuildId
import alfred.web.core.Builds
import alfred.web.core.build.BuildConfig
import alfred.web.core.build.BuildId
import alfred.web.core.build.Builds
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service

Loading…
Cancel
Save