major reordering, making stuff fit for native compilation
							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
	
	 Josha von Gizycki
						Josha von Gizycki