103 lines
2.5 KiB
Go
103 lines
2.5 KiB
Go
|
package runner
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"log"
|
||
|
workflow "runner/internal/workflow"
|
||
|
)
|
||
|
|
||
|
type Runner struct {
|
||
|
Labels map[string]string
|
||
|
Driver ContainerDriver
|
||
|
Runs int
|
||
|
}
|
||
|
|
||
|
func NewRunner(driver ContainerDriver, labels map[string]string) Runner {
|
||
|
return Runner{
|
||
|
Driver: driver,
|
||
|
Labels: labels,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (r *Runner) GetContainerName() string {
|
||
|
return fmt.Sprintf("runner-%d", r.Runs)
|
||
|
}
|
||
|
|
||
|
func (r *Runner) GetImageUriByLabel(label string) string {
|
||
|
uri, exists := r.Labels[label]
|
||
|
|
||
|
if exists {
|
||
|
return uri
|
||
|
}
|
||
|
|
||
|
return "debian:latest"
|
||
|
}
|
||
|
|
||
|
// Executes a workflow using the runner.
|
||
|
//
|
||
|
// This is the high-level call that will set up the container
|
||
|
// that the jobs will be executed in, run the jobs's steps and
|
||
|
// tear down the container once no longer useful.
|
||
|
func (r *Runner) Execute(workflow workflow.Workflow) map[string]error {
|
||
|
log.Printf("Executing workflow: %s", workflow.SourcePath)
|
||
|
|
||
|
errors := map[string]error{}
|
||
|
|
||
|
for jobLabel, job := range workflow.Jobs {
|
||
|
runnerImage := r.GetImageUriByLabel(job.RunsOn)
|
||
|
containerName := r.GetContainerName()
|
||
|
|
||
|
log.Printf("Using image %s (label: %s)", runnerImage, job.RunsOn)
|
||
|
|
||
|
if pullError := r.PullContainer(runnerImage); pullError != nil {
|
||
|
errors[jobLabel] = pullError
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if runError := r.RunJobInContainer(runnerImage, containerName, job); runError != nil {
|
||
|
errors[jobLabel] = runError
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return errors
|
||
|
}
|
||
|
|
||
|
// Pulls the container from the registry provided its image uri.
|
||
|
func (r *Runner) PullContainer(uri string) error {
|
||
|
return r.Driver.Pull(uri)
|
||
|
}
|
||
|
|
||
|
// Starts a container from the given image uri with the given name.
|
||
|
func (r *Runner) StartContainer(uri string, containerName string) error {
|
||
|
return r.Driver.Start(uri, containerName)
|
||
|
}
|
||
|
|
||
|
// Executes a command within the given container.
|
||
|
func (r *Runner) RunCommandInContainer(containerId string, command string) error {
|
||
|
return r.Driver.Exec(containerId, command)
|
||
|
}
|
||
|
|
||
|
// Executes a job within a container.
|
||
|
//
|
||
|
// The container is started before the job steps are run and cleaned up after.
|
||
|
func (r *Runner) RunJobInContainer(imageUri string, containerId string, job workflow.Job) error {
|
||
|
r.StartContainer(imageUri, containerId)
|
||
|
defer r.StopContainer(containerId)
|
||
|
|
||
|
log.Printf("Started %s", containerId)
|
||
|
for _, step := range job.Steps {
|
||
|
log.Printf("Run: %s", step.Run)
|
||
|
r.RunCommandInContainer(containerId, step.Run)
|
||
|
}
|
||
|
|
||
|
log.Printf("Cleaning up %s", containerId)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Stops the given container.
|
||
|
func (r *Runner) StopContainer(containerName string) {
|
||
|
r.Driver.Stop(containerName)
|
||
|
}
|