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) }