parent
a70ae91ce4
commit
928a08b842
@ -0,0 +1,22 @@
|
|||||||
|
package alfred.web.core
|
||||||
|
|
||||||
|
import alfred.web.core.build.BuildId
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AlfredHome(
|
||||||
|
@Value("\${ALFRED_HOME}")
|
||||||
|
private val home: String,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val homePath = Paths.get(home)
|
||||||
|
|
||||||
|
fun logsDir() =
|
||||||
|
homePath.resolve("logs")
|
||||||
|
|
||||||
|
fun buildConfig(buildId: BuildId) =
|
||||||
|
homePath.resolve("builds/${buildId}.properties")
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package alfred.web.core.process
|
||||||
|
|
||||||
|
import alfred.web.core.Handle
|
||||||
|
import alfred.web.core.Handles
|
||||||
|
import alfred.web.core.build.BuildContext
|
||||||
|
import alfred.web.core.build.BuildId
|
||||||
|
import alfred.web.core.build.Workspace
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class Git(
|
||||||
|
val processes: Processes,
|
||||||
|
val handles: Handles,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun clone(ctx: BuildContext, ws: Workspace) {
|
||||||
|
ctx.log("cloning ${ctx.config.gitRepo} into $ws")
|
||||||
|
|
||||||
|
val proc = processes.builder(ctx.config, ctx.logFile, "")
|
||||||
|
.command("git", "clone", ctx.config.gitRepo, ".")
|
||||||
|
.directory(ws.toFile())
|
||||||
|
.start()
|
||||||
|
handles.add(Handle(proc.toHandle(), ctx.buildId))
|
||||||
|
|
||||||
|
val cloneSuccess = proc.waitFor(ctx.config.gitCloneTimeout, TimeUnit.SECONDS)
|
||||||
|
if (!cloneSuccess) {
|
||||||
|
throw FailedToClone(ctx.buildId, ctx.config.gitRepo ?: "[no repo configured]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkout(ctx: BuildContext, ws: Workspace) {
|
||||||
|
ctx.log("checkout rev ${ctx.rev}")
|
||||||
|
|
||||||
|
val proc = processes.builder(ctx.config, ctx.logFile, "")
|
||||||
|
.command("git", "checkout", ctx.rev)
|
||||||
|
.directory(ws.toFile())
|
||||||
|
.start()
|
||||||
|
handles.add(Handle(proc.toHandle(), ctx.buildId))
|
||||||
|
|
||||||
|
val checkoutSuccess = proc.waitFor(ctx.config.gitCloneTimeout, TimeUnit.SECONDS)
|
||||||
|
if (!checkoutSuccess) {
|
||||||
|
throw FailedToCheckout(ctx.buildId, ctx.config.gitRepo!!, ctx.rev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class GitException(msg: String): Exception(msg)
|
||||||
|
|
||||||
|
private class FailedToClone(
|
||||||
|
buildId: BuildId,
|
||||||
|
gitRepo: String
|
||||||
|
) : GitException(
|
||||||
|
"failed to clone $gitRepo for build id $buildId"
|
||||||
|
)
|
||||||
|
|
||||||
|
private class FailedToCheckout(
|
||||||
|
buildId: BuildId,
|
||||||
|
gitRepo: String,
|
||||||
|
rev: String
|
||||||
|
) : GitException(
|
||||||
|
"failed to checkout revision $rev on repo $gitRepo for build id $buildId"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package alfred.web.core.process
|
||||||
|
|
||||||
|
import alfred.web.core.Handle
|
||||||
|
import alfred.web.core.Handles
|
||||||
|
import alfred.web.core.build.BuildContext
|
||||||
|
import alfred.web.core.build.Workspace
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class Script(
|
||||||
|
val handles: Handles,
|
||||||
|
val processes: Processes
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(ctx: BuildContext, ws: Workspace, scriptFile: File): Process {
|
||||||
|
ctx.log("Running build file: ${scriptFile.name}")
|
||||||
|
|
||||||
|
val scriptProcess = processes.builder(ctx.config, ctx.logFile, ctx.rev)
|
||||||
|
.command(scriptFile.absolutePath)
|
||||||
|
.directory(ws.toFile())
|
||||||
|
.start()
|
||||||
|
handles.add(Handle(scriptProcess.toHandle(), ctx.buildId))
|
||||||
|
|
||||||
|
return scriptProcess
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exists(scriptFile: File) = scriptFile.exists()
|
||||||
|
|
||||||
|
fun onExit(process: Process, block: () -> Unit) {
|
||||||
|
process.onExit().whenComplete { _, _ -> block() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package alfred.web.core
|
||||||
|
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
fun <T : Any> eventPublisher(eventType: KClass<T>): ApplicationEventPublisher {
|
||||||
|
val eventPublisher = mockk<ApplicationEventPublisher>()
|
||||||
|
justRun { eventPublisher.publishEvent(any(eventType)) }
|
||||||
|
return eventPublisher
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package alfred.web.core.runner
|
||||||
|
|
||||||
|
import alfred.web.core.AlfredHome
|
||||||
|
import alfred.web.core.build.Builds
|
||||||
|
import alfred.web.core.build.Workspaces
|
||||||
|
import alfred.web.core.event.BuildFinished
|
||||||
|
import alfred.web.core.eventPublisher
|
||||||
|
import alfred.web.core.process.Git
|
||||||
|
import alfred.web.core.process.ProcessEnvironment
|
||||||
|
import alfred.web.core.process.Processes
|
||||||
|
import alfred.web.core.process.Script
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
class GitRunnerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun runs(@TempDir logsDir: Path) {
|
||||||
|
// Given
|
||||||
|
val eventPublisher = eventPublisher(BuildFinished::class)
|
||||||
|
|
||||||
|
val git = mockk<Git>()
|
||||||
|
justRun { git.clone(any(), any()) }
|
||||||
|
justRun { git.checkout(any(), any()) }
|
||||||
|
|
||||||
|
val script = mockk<Script>()
|
||||||
|
every { script.execute(any(), any(), any()) } returns (mockk<Process>(relaxed = true))
|
||||||
|
every { script.exists(any()) } returns (true)
|
||||||
|
|
||||||
|
val workspacesSpy = spyk(Workspaces())
|
||||||
|
|
||||||
|
val homeDir = Paths.get("").toAbsolutePath().resolve("src/test/resources/home-1")
|
||||||
|
val home = spyk(AlfredHome(homeDir.toFile().absolutePath))
|
||||||
|
every { home.logsDir() } returns logsDir
|
||||||
|
|
||||||
|
val runner = GitRunner(
|
||||||
|
builds = Builds(home),
|
||||||
|
processes = Processes(ProcessEnvironment()),
|
||||||
|
workspaces = workspacesSpy,
|
||||||
|
eventPublisher = eventPublisher,
|
||||||
|
git = git,
|
||||||
|
script = script
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
runner.run(
|
||||||
|
buildId = "simple-git",
|
||||||
|
rev = "master"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify(timeout = 1_000, exactly = 1) {
|
||||||
|
git.clone(any(), any())
|
||||||
|
git.checkout(any(), any())
|
||||||
|
|
||||||
|
listOf("pre.sh", "job.sh", "post.sh").forEach { scriptName ->
|
||||||
|
script.exists(match { it.name == scriptName })
|
||||||
|
script.execute(any(), any(), match { it.name == scriptName })
|
||||||
|
}
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(any(BuildFinished::class))
|
||||||
|
workspacesSpy.cleanUp(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package alfred.web.core.runner
|
||||||
|
|
||||||
|
import alfred.web.core.AlfredHome
|
||||||
|
import alfred.web.core.build.Builds
|
||||||
|
import alfred.web.core.build.Workspaces
|
||||||
|
import alfred.web.core.event.BuildFinished
|
||||||
|
import alfred.web.core.eventPublisher
|
||||||
|
import alfred.web.core.process.Script
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
import org.springframework.test.web.client.ExpectedCount.once
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptRunnerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun runs(@TempDir logDir: Path) {
|
||||||
|
// Given
|
||||||
|
val home = spyk(
|
||||||
|
AlfredHome(
|
||||||
|
Paths.get("").toAbsolutePath().resolve("src/test/resources/home-1").toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
every { home.logsDir() } returns logDir
|
||||||
|
|
||||||
|
val eventPublisher = eventPublisher(BuildFinished::class)
|
||||||
|
|
||||||
|
val script = mockk<Script>()
|
||||||
|
every { script.execute(any(), any(), any()) } returns mockk<Process>(relaxed = true)
|
||||||
|
val onExit = slot<() -> Unit>()
|
||||||
|
every { script.onExit(any(), capture(onExit)) } answers { onExit.captured() }
|
||||||
|
|
||||||
|
val workspaces = spyk(Workspaces())
|
||||||
|
|
||||||
|
val runner = ScriptRunner(
|
||||||
|
builds = Builds(home),
|
||||||
|
workspaces = workspaces,
|
||||||
|
eventPublisher = eventPublisher,
|
||||||
|
script = script
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
runner.run(
|
||||||
|
buildId = "simple-script",
|
||||||
|
rev = null
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify(timeout = 1_000, exactly = 1) {
|
||||||
|
script.execute(any(), any(), match { it.name == "some-script" })
|
||||||
|
|
||||||
|
workspaces.cleanUp(any())
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(any(BuildFinished::class))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
user=alfred
|
||||||
|
workspace=/tmp
|
||||||
|
apikey=Mellon
|
||||||
|
|
||||||
|
git.repo.url=some.url
|
@ -0,0 +1,5 @@
|
|||||||
|
user=alfred
|
||||||
|
workspace=/tmp
|
||||||
|
apikey=Mellon
|
||||||
|
|
||||||
|
script=some-script
|
Loading…
Reference in new issue