parent
2f65800ba6
commit
4c070a3745
@ -1,14 +1,17 @@
|
||||
package de.joshavg
|
||||
package alfred.http
|
||||
|
||||
import alfred.BuildConfig
|
||||
import alfred.BuildId
|
||||
import alfred.Builds
|
||||
import javax.enterprise.context.ApplicationScoped
|
||||
import javax.inject.Inject
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@ApplicationScoped
|
||||
class Security(
|
||||
@Inject
|
||||
val builds: Builds
|
||||
) {
|
||||
class Security {
|
||||
|
||||
@field:Inject
|
||||
lateinit var builds: Builds
|
||||
|
||||
fun requireKey(build: BuildId, apikey: String?, block: (BuildConfig) -> Any): Response {
|
||||
val buildConfig = builds.buildConfig(build)
|
@ -0,0 +1,154 @@
|
||||
package alfred.running
|
||||
|
||||
import alfred.BuildConfig
|
||||
import alfred.BuildId
|
||||
import alfred.Builds
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.enterprise.context.ApplicationScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScoped
|
||||
class GitRunner {
|
||||
|
||||
@field:Inject
|
||||
lateinit var builds: Builds
|
||||
|
||||
@field:Inject
|
||||
lateinit var handles: Handles
|
||||
|
||||
val scriptsDir = ".alfred"
|
||||
|
||||
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")
|
||||
|
||||
val ctx = BuildContext(
|
||||
buildId = buildId,
|
||||
config = config,
|
||||
wsId = UUID.randomUUID().toString(),
|
||||
logFile = logFile,
|
||||
rev = rev
|
||||
)
|
||||
|
||||
logFile.header(buildId, rev, ctx.workspace)
|
||||
|
||||
assertEmptyWorkspace(ctx)
|
||||
|
||||
Thread {
|
||||
try {
|
||||
clone(ctx)
|
||||
execScripts(ctx)
|
||||
} finally {
|
||||
deleteWorkspace(ctx)
|
||||
logFile.footer()
|
||||
}
|
||||
}.start()
|
||||
|
||||
return ProcessInfo(-1, logFile)
|
||||
}
|
||||
|
||||
private fun clone(ctx: BuildContext) {
|
||||
logger.info("build ${ctx.buildId}: cloning ${ctx.config.gitRepo} into ${ctx.workspace}")
|
||||
|
||||
val process = processBuilder(ctx.config, ctx.logFile, "")
|
||||
.command("git", "clone", ctx.config.gitRepo, ".")
|
||||
.directory(ctx.workspace.toFile())
|
||||
.start()
|
||||
handles.add(Handle(process.toHandle(), ctx.buildId))
|
||||
val cloneSuccess = process.waitFor(30, TimeUnit.SECONDS)
|
||||
|
||||
logger.info("build ${ctx.buildId}: checkout rev ${ctx.rev}")
|
||||
processBuilder(ctx.config, ctx.logFile, "")
|
||||
.command("git", "checkout", ctx.rev)
|
||||
.directory(ctx.workspace.toFile())
|
||||
.start()
|
||||
.waitFor()
|
||||
|
||||
if (!cloneSuccess) {
|
||||
throw FailedToClone(ctx.buildId, ctx.config.gitRepo ?: "[no repo configured]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun execScripts(ctx: BuildContext) {
|
||||
val scriptFiles = listOf("pre.sh", "job.sh", "post.sh")
|
||||
|
||||
logger.info("build ${ctx.buildId}: looking for scripts $scriptFiles in $scriptsDir/")
|
||||
|
||||
scriptFiles.forEach { script ->
|
||||
if (shFile(ctx, script).exists()) {
|
||||
logger.info("build ${ctx.buildId}: found script $script, running it")
|
||||
|
||||
ctx.logFile.append("\nRunning build file: $script\n")
|
||||
|
||||
val scriptProcess = processBuilder(ctx.config, ctx.logFile, ctx.rev)
|
||||
.command("$scriptsDir/$script")
|
||||
.directory(ctx.workspace.toFile())
|
||||
.start()
|
||||
handles.add(Handle(scriptProcess.toHandle(), ctx.buildId))
|
||||
|
||||
val ret = scriptProcess.waitFor()
|
||||
logger.info("build ${ctx.buildId}: script $script returned $ret")
|
||||
|
||||
ctx.logFile.append("\n$script returned $ret\n")
|
||||
} else {
|
||||
ctx.logFile.append("\nBuild file $scriptsDir/$script not found\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shFile(ctx: BuildContext, name: String) =
|
||||
ctx.workspace.resolve(scriptsDir).resolve(name).toFile()
|
||||
|
||||
private fun deleteWorkspace(ctx: BuildContext) {
|
||||
ctx.workspace.toFile().deleteRecursively()
|
||||
}
|
||||
|
||||
private fun assertEmptyWorkspace(config: BuildContext) {
|
||||
val workspace = config.workspace.toFile()
|
||||
|
||||
if (!workspace.exists()) {
|
||||
workspace.mkdirs()
|
||||
}
|
||||
|
||||
val wsList = workspace.list()
|
||||
if (wsList != null && wsList.isNotEmpty()) {
|
||||
throw WorkspaceIsNotEmpty(config.workspace.toString())
|
||||
}
|
||||
}
|
||||
|
||||
val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
}
|
||||
|
||||
data class BuildContext(
|
||||
val config: BuildConfig,
|
||||
val wsId: String,
|
||||
val logFile: LogFile,
|
||||
val rev: String,
|
||||
val buildId: BuildId
|
||||
) {
|
||||
val workspace: Path = Paths.get(config.workspace, wsId)
|
||||
}
|
||||
|
||||
class WorkspaceIsNotEmpty(
|
||||
private val ws: String
|
||||
) : RuntimeException() {
|
||||
override fun toString() =
|
||||
"${this::class}: workspace $ws is not empty"
|
||||
}
|
||||
|
||||
class FailedToClone(
|
||||
private val buildId: BuildId,
|
||||
private val gitRepo: String
|
||||
) : RuntimeException() {
|
||||
override fun toString() =
|
||||
"${this::class}: failed to clone $gitRepo for build id $buildId"
|
||||
}
|
@ -1,40 +1,39 @@
|
||||
package de.joshavg
|
||||
package alfred.running
|
||||
|
||||
import alfred.BuildId
|
||||
import alfred.Builds
|
||||
import alfred.http.Security
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.time.ZonedDateTime
|
||||
import java.nio.file.Paths
|
||||
import javax.enterprise.context.ApplicationScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScoped
|
||||
class ScriptRunner(
|
||||
@Inject
|
||||
val builds: Builds,
|
||||
@Inject
|
||||
val handles: Handles
|
||||
) {
|
||||
class ScriptRunner {
|
||||
|
||||
@field:Inject
|
||||
lateinit var builds: Builds
|
||||
|
||||
@field:Inject
|
||||
lateinit var handles: Handles
|
||||
|
||||
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.asPublic()}")
|
||||
logger.info("preparing process for build $buildId with config $config")
|
||||
logger.info("log file: $logFile")
|
||||
|
||||
logFileHeader(logFile, buildId, rev)
|
||||
logFile.header(buildId, rev, Paths.get(config.workspace))
|
||||
|
||||
val process = ProcessBuilder(config.script)
|
||||
val process = processBuilder(config, logFile, rev)
|
||||
.command(config.script)
|
||||
.directory(File(config.workspace))
|
||||
.redirectOutput(logFile)
|
||||
.redirectError(logFile)
|
||||
.apply {
|
||||
environment().putAll(config.env)
|
||||
environment()["ALFRED_LOG_FILE"] = logFile.absolutePath
|
||||
environment()["ALFRED_REV"] = rev
|
||||
}
|
||||
.start()
|
||||
handles.add(Handle(process.toHandle(), buildId))
|
||||
process.onExit().whenComplete { _, _ -> logFile.footer() }
|
||||
|
||||
val pid = process.pid()
|
||||
|
@ -0,0 +1,40 @@
|
||||
package alfred.running
|
||||
|
||||
import alfred.BuildConfig
|
||||
import alfred.BuildId
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
fun processBuilder(config: BuildConfig, logFile: File, rev: String): ProcessBuilder =
|
||||
ProcessBuilder()
|
||||
.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile))
|
||||
.redirectError(ProcessBuilder.Redirect.appendTo(logFile))
|
||||
.apply {
|
||||
environment().putAll(config.env)
|
||||
environment()["ALFRED_LOG_FILE"] = logFile.absolutePath
|
||||
environment()["ALFRED_REV"] = rev
|
||||
}
|
||||
|
||||
typealias LogFile = File
|
||||
|
||||
fun LogFile.header(buildId: BuildId, rev: String, workspace: Path) {
|
||||
this.append("At your service.")
|
||||
this.append("Build $buildId, rev [$rev] started at ${ZonedDateTime.now()}")
|
||||
this.append("Workspace directory is: $workspace\n")
|
||||
}
|
||||
|
||||
fun LogFile.footer() {
|
||||
this.append("Build finished at ${ZonedDateTime.now()}")
|
||||
}
|
||||
|
||||
fun LogFile.append(content: String) =
|
||||
this.appendText(
|
||||
content.lines().joinToString(separator = "\n") {
|
||||
if (it.trim().isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
"Alfred: $it"
|
||||
}
|
||||
} + "\n"
|
||||
)
|
@ -1,132 +0,0 @@
|
||||
package de.joshavg
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.enterprise.context.ApplicationScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScoped
|
||||
class GitRunner(
|
||||
@Inject
|
||||
val builds: Builds,
|
||||
@Inject
|
||||
val handles: Handles
|
||||
) {
|
||||
|
||||
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.asPublic()}")
|
||||
logger.info("log file: $logFile")
|
||||
|
||||
logFileHeader(logFile, buildId, rev)
|
||||
|
||||
assertEmptyWorkspace(config)
|
||||
|
||||
Thread {
|
||||
try {
|
||||
clone(config, logFile, config.gitRepo!!, rev, buildId)
|
||||
execScripts(config, logFile, rev, buildId)
|
||||
} finally {
|
||||
cleanupWorkspace(config, buildId)
|
||||
}
|
||||
}.start()
|
||||
|
||||
return ProcessInfo(-1, logFile)
|
||||
}
|
||||
|
||||
private fun clone(
|
||||
config: BuildConfig,
|
||||
logFile: File,
|
||||
gitRepo: String,
|
||||
rev: String,
|
||||
buildId: BuildId
|
||||
) {
|
||||
logger.info("build $buildId: cloning ${config.gitRepo} into ${config.workspace}")
|
||||
|
||||
val process = processBuilder(config, logFile, "")
|
||||
.command("git", "clone", config.gitRepo, ".")
|
||||
.start()
|
||||
handles.add(Handle(process.toHandle(), buildId))
|
||||
val cloneSuccess = process.waitFor(30, TimeUnit.SECONDS)
|
||||
|
||||
logger.info("build $buildId: checkout rev $rev")
|
||||
processBuilder(config, logFile, "")
|
||||
.command("git", "checkout", rev)
|
||||
.start()
|
||||
.waitFor()
|
||||
|
||||
if (!cloneSuccess) {
|
||||
throw FailedToClone(buildId, gitRepo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun execScripts(config: BuildConfig, logFile: File, rev: String, buildId: BuildId) {
|
||||
val scriptFiles = listOf("pre.sh", "job.sh", "post.sh")
|
||||
|
||||
logger.info("build $buildId: looking for scripts $scriptFiles in .alfred/")
|
||||
|
||||
scriptFiles.forEach { script ->
|
||||
if (Paths.get(config.workspace, ".alfred", script).toFile().exists()) {
|
||||
logger.info("build $buildId: found script $script, running it")
|
||||
|
||||
val scriptProcess = processBuilder(config, logFile, rev)
|
||||
.command(".alfred/$script")
|
||||
.start()
|
||||
handles.add(Handle(scriptProcess.toHandle(), buildId))
|
||||
|
||||
val ret = scriptProcess.waitFor()
|
||||
logger.info("build $buildId: script $script returned $ret")
|
||||
|
||||
logFile.appendText("\n$script returned $ret\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupWorkspace(config: BuildConfig, buildId: BuildId) {
|
||||
val wsList = File(config.workspace).list()
|
||||
if (wsList != null && wsList.isNotEmpty()) {
|
||||
logger.info("build $buildId: cleaning up workspace ${config.workspace}")
|
||||
|
||||
val ws = File(config.workspace)
|
||||
ws.deleteRecursively()
|
||||
ws.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertEmptyWorkspace(config: BuildConfig) {
|
||||
val workspace = File(config.workspace)
|
||||
|
||||
if (!workspace.exists()) {
|
||||
workspace.mkdirs()
|
||||
}
|
||||
|
||||
val wsList = workspace.list()
|
||||
if (wsList != null && wsList.isNotEmpty()) {
|
||||
throw WorkspaceIsNotEmpty(config.workspace)
|
||||
}
|
||||
}
|
||||
|
||||
val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
}
|
||||
|
||||
|
||||
class WorkspaceIsNotEmpty(
|
||||
private val ws: String
|
||||
) : RuntimeException() {
|
||||
override fun toString() =
|
||||
"${this::class}: workspace $ws is not empty"
|
||||
}
|
||||
|
||||
class FailedToClone(
|
||||
private val buildId: BuildId,
|
||||
private val gitRepo: String
|
||||
) : RuntimeException() {
|
||||
override fun toString() =
|
||||
"${this::class}: failed to clone $gitRepo for build id $buildId"
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package de.joshavg
|
||||
|
||||
import java.io.File
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
fun processBuilder(config: BuildConfig, logFile: File, rev: String): ProcessBuilder =
|
||||
ProcessBuilder()
|
||||
.directory(File(config.workspace))
|
||||
.redirectOutput(logFile)
|
||||
.redirectError(logFile)
|
||||
.apply {
|
||||
environment().putAll(config.env)
|
||||
environment()["ALFRED_LOG_FILE"] = logFile.absolutePath
|
||||
environment()["ALFRED_REV"] = rev
|
||||
}
|
||||
|
||||
fun logFileHeader(logFile: File, buildId : BuildId, rev: String) {
|
||||
logFile.appendText("THE ALFRED!\n")
|
||||
logFile.appendText("Build $buildId, rev $rev started at ${ZonedDateTime.now()}\n\n")
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package de.joshavg
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import io.restassured.RestAssured.given
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@QuarkusTest
|
||||
open class ExampleResourceTest {
|
||||
|
||||
@Test
|
||||
fun testHelloEndpoint() {
|
||||
given()
|
||||
.`when`().get("/hello")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body(`is`("hello"))
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package de.joshavg
|
||||
|
||||
import io.quarkus.test.junit.NativeImageTest
|
||||
|
||||
@NativeImageTest
|
||||
open class NativeExampleResourceIT : ExampleResourceTest()
|
Loading…
Reference in new issue