refactor: extract driver into own module

This commit is contained in:
Marc 2024-08-22 23:11:00 -04:00
parent e06b46e32d
commit 27715ecebb
Signed by: marc
GPG key ID: 048E042F22B5DC79
9 changed files with 130 additions and 126 deletions

View file

@ -1,6 +1,7 @@
package commands package commands
import ( import (
driver "courgette/internal/driver"
logger "courgette/internal/logging" logger "courgette/internal/logging"
runner "courgette/internal/runner" runner "courgette/internal/runner"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
@ -9,7 +10,7 @@ import (
) )
func ExecuteWorkflow(configuration Configuration, workflowFile string) error { func ExecuteWorkflow(configuration Configuration, workflowFile string) error {
driver, err := runner.NewDriver(configuration.Containers.Driver) driver, err := driver.NewDriver(configuration.Containers.Driver)
if err != nil { if err != nil {
return err return err

View file

@ -1,4 +1,4 @@
package runner package driver
import ( import (
"errors" "errors"

View file

@ -0,0 +1,98 @@
package driver
import (
"fmt"
"strings"
)
type MockCall struct {
Fname string
Args []string
}
type MockDriver struct {
Calls map[string][]MockCall
MockedCalls map[string]map[string]CommandResult
}
func NewMockDriver() MockDriver {
return MockDriver{
Calls: map[string][]MockCall{},
MockedCalls: map[string]map[string]CommandResult{},
}
}
func (d *MockDriver) Pull(uri string) error {
if _, init := d.Calls["Pull"]; !init {
d.Calls["Pull"] = []MockCall{}
}
d.Calls["Pull"] = append(d.Calls["Pull"], MockCall{Fname: "Pull", Args: []string{uri}})
return nil
}
func (d *MockDriver) Start(uri string, containerName string) error {
if _, init := d.Calls["Start"]; !init {
d.Calls["Start"] = []MockCall{}
}
d.Calls["Start"] = append(d.Calls["Start"], MockCall{Fname: "Start", Args: []string{uri, containerName}})
return nil
}
func (d *MockDriver) Stop(uri string) error {
if _, init := d.Calls["Stop"]; !init {
d.Calls["Stop"] = []MockCall{}
}
d.Calls["Stop"] = append(d.Calls["Stop"], MockCall{Fname: "Stop", Args: []string{uri}})
return nil
}
func (d *MockDriver) Exec(containerName string, command string, options CommandOptions) CommandResult {
if _, init := d.Calls["Exec"]; !init {
d.Calls["Exec"] = []MockCall{}
}
args := []string{containerName, command, options.Cwd, fmt.Sprintf("%#v", options.Env)}
d.Calls["Exec"] = append(d.Calls["Exec"], MockCall{Fname: "Exec", Args: args})
mockKeys := []string{
fmt.Sprintf("nthcall::%d", len(d.Calls["Exec"])),
fmt.Sprintf("withargs::%s", strings.Join(args, " ")),
}
for _, mockKey := range mockKeys {
if _, mocked := d.MockedCalls["Exec"][mockKey]; mocked {
return d.MockedCalls["Exec"][mockKey]
}
}
return CommandResult{}
}
// Mocks a call to <fn> with arguments <args> to return <returnValue>.
//
// The mocked call is reused as long as it's defined within the mock driver.
func (d *MockDriver) WithMockedCall(fn string, returnValue CommandResult, args ...string) {
mockKey := fmt.Sprintf("withargs::%s", strings.Join(args, " "))
if _, initialized := d.MockedCalls[fn]; !initialized {
d.MockedCalls[fn] = map[string]CommandResult{}
}
d.MockedCalls[fn][mockKey] = returnValue
}
// Mocks the nth call to <fn> to return <returnValue>.
//
// The mocked call is reused as long as it's defined within the mock driver.
func (d *MockDriver) WithNthMockedCall(fn string, callIndex int, returnValue CommandResult) {
mockKey := fmt.Sprintf("nthcall::%d", callIndex)
if _, initialized := d.MockedCalls[fn]; !initialized {
d.MockedCalls[fn] = map[string]CommandResult{}
}
d.MockedCalls[fn][mockKey] = returnValue
}

View file

@ -1,7 +1,7 @@
// Podman driver // Podman driver
// //
// Abstracts interactions with Podman commands via the ContainerDriver interface. // Abstracts interactions with Podman commands via the ContainerDriver interface.
package runner package driver
import ( import (
logger "courgette/internal/logging" logger "courgette/internal/logging"

View file

@ -1,4 +1,4 @@
package runner package driver
import ( import (
"encoding/json" "encoding/json"

View file

@ -1,98 +0,0 @@
package runner
import (
"fmt"
"strings"
)
type MockCall struct {
fname string
args []string
}
type MockDriver struct {
calls map[string][]MockCall
mockedCalls map[string]map[string]CommandResult
}
func NewMockDriver() MockDriver {
return MockDriver{
calls: map[string][]MockCall{},
mockedCalls: map[string]map[string]CommandResult{},
}
}
func (d *MockDriver) Pull(uri string) error {
if _, init := d.calls["Pull"]; !init {
d.calls["Pull"] = []MockCall{}
}
d.calls["Pull"] = append(d.calls["Pull"], MockCall{fname: "Pull", args: []string{uri}})
return nil
}
func (d *MockDriver) Start(uri string, containerName string) error {
if _, init := d.calls["Start"]; !init {
d.calls["Start"] = []MockCall{}
}
d.calls["Start"] = append(d.calls["Start"], MockCall{fname: "Start", args: []string{uri, containerName}})
return nil
}
func (d *MockDriver) Stop(uri string) error {
if _, init := d.calls["Stop"]; !init {
d.calls["Stop"] = []MockCall{}
}
d.calls["Stop"] = append(d.calls["Stop"], MockCall{fname: "Stop", args: []string{uri}})
return nil
}
func (d *MockDriver) Exec(containerName string, command string, options CommandOptions) CommandResult {
if _, init := d.calls["Exec"]; !init {
d.calls["Exec"] = []MockCall{}
}
args := []string{containerName, command, options.Cwd, fmt.Sprintf("%#v", options.Env)}
d.calls["Exec"] = append(d.calls["Exec"], MockCall{fname: "Exec", args: args})
mockKeys := []string{
fmt.Sprintf("nthcall::%d", len(d.calls["Exec"])),
fmt.Sprintf("withargs::%s", strings.Join(args, " ")),
}
for _, mockKey := range mockKeys {
if _, mocked := d.mockedCalls["Exec"][mockKey]; mocked {
return d.mockedCalls["Exec"][mockKey]
}
}
return CommandResult{}
}
// Mocks a call to <fn> with arguments <args> to return <returnValue>.
//
// The mocked call is reused as long as it's defined within the mock driver.
func (d *MockDriver) WithMockedCall(fn string, returnValue CommandResult, args ...string) {
mockKey := fmt.Sprintf("withargs::%s", strings.Join(args, " "))
if _, initialized := d.mockedCalls[fn]; !initialized {
d.mockedCalls[fn] = map[string]CommandResult{}
}
d.mockedCalls[fn][mockKey] = returnValue
}
// Mocks the nth call to <fn> to return <returnValue>.
//
// The mocked call is reused as long as it's defined within the mock driver.
func (d *MockDriver) WithNthMockedCall(fn string, callIndex int, returnValue CommandResult) {
mockKey := fmt.Sprintf("nthcall::%d", callIndex)
if _, initialized := d.mockedCalls[fn]; !initialized {
d.mockedCalls[fn] = map[string]CommandResult{}
}
d.mockedCalls[fn][mockKey] = returnValue
}

View file

@ -2,6 +2,7 @@ package runner
import ( import (
"context" "context"
driver "courgette/internal/driver"
logger "courgette/internal/logging" logger "courgette/internal/logging"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
"errors" "errors"
@ -11,13 +12,13 @@ import (
type Runner struct { type Runner struct {
Labels map[string]string Labels map[string]string
Driver ContainerDriver Driver driver.ContainerDriver
Runs int Runs int
// Deferred tasks, in order their were scheduled. // Deferred tasks, in order their were scheduled.
deferred *DeferredTaskManager deferred *DeferredTaskManager
} }
func NewRunner(driver ContainerDriver, labels map[string]string) Runner { func NewRunner(driver driver.ContainerDriver, labels map[string]string) Runner {
return Runner{ return Runner{
Driver: driver, Driver: driver,
Labels: labels, Labels: labels,
@ -104,7 +105,7 @@ func (r Runner) runJob(jobContext context.Context, jobTracker *TaskTracker, jobW
// If the command raises an error while in the container or fails to run // If the command raises an error while in the container or fails to run
// the command at all, an error is returned, otherwise nil. // the command at all, an error is returned, otherwise nil.
func (r *Runner) RunCommandInContainer(containerId string, command string, stepCwd string, stepEnv map[string]string) error { func (r *Runner) RunCommandInContainer(containerId string, command string, stepCwd string, stepEnv map[string]string) error {
result := r.Driver.Exec(containerId, command, CommandOptions{Cwd: stepCwd, Env: stepEnv}) result := r.Driver.Exec(containerId, command, driver.CommandOptions{Cwd: stepCwd, Env: stepEnv})
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error

View file

@ -2,6 +2,7 @@ package runner
import ( import (
"context" "context"
driver "courgette/internal/driver"
logger "courgette/internal/logging" logger "courgette/internal/logging"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
"errors" "errors"
@ -14,8 +15,8 @@ func init() {
} }
func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) { func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) {
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{ExitCode: 0, Error: errors.New("test")}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{ExitCode: 0, Error: errors.New("test")}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{}))
runner := Runner{ runner := Runner{
Driver: &mockDriver, Driver: &mockDriver,
@ -29,8 +30,8 @@ func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) {
} }
func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *testing.T) { func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *testing.T) {
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{}))
runner := Runner{ runner := Runner{
Driver: &mockDriver, Driver: &mockDriver,
@ -44,8 +45,8 @@ func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *test
} }
func TestRunJobInContainerSchedulesStoppingContainers(t *testing.T) { func TestRunJobInContainerSchedulesStoppingContainers(t *testing.T) {
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{ExitCode: 1, Error: nil}, "test-container", "test-command", ".", fmt.Sprintf("%#v", map[string]string{}))
runner := NewRunner(&mockDriver, map[string]string{}) runner := NewRunner(&mockDriver, map[string]string{})

View file

@ -2,6 +2,7 @@ package runner
import ( import (
"context" "context"
driver "courgette/internal/driver"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
"errors" "errors"
"fmt" "fmt"
@ -18,14 +19,14 @@ jobs:
` `
workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) workflow, _ := workflow.FromYamlBytes([]byte(workflowSample))
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) runner := NewRunner(&mockDriver, map[string]string{"test": "test"})
jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext := context.WithValue(context.Background(), "workflow", *workflow)
jobContext = context.WithValue(jobContext, "currentJob", workflow.Jobs["jobA"]) jobContext = context.WithValue(jobContext, "currentJob", workflow.Jobs["jobA"])
mockDriver.WithNthMockedCall("Exec", 1, CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}) mockDriver.WithNthMockedCall("Exec", 1, driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1})
task := runner.RunWorkflow(*workflow) task := runner.RunWorkflow(*workflow)
if !task.Failed() { if !task.Failed() {
@ -43,14 +44,14 @@ jobs:
` `
workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) workflow, _ := workflow.FromYamlBytes([]byte(workflowSample))
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) runner := NewRunner(&mockDriver, map[string]string{"test": "test"})
jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext := context.WithValue(context.Background(), "workflow", *workflow)
jobContext = context.WithValue(jobContext, "currentJob", workflow.Jobs["jobA"]) jobContext = context.WithValue(jobContext, "currentJob", workflow.Jobs["jobA"])
mockDriver.WithNthMockedCall("Exec", 1, CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}) mockDriver.WithNthMockedCall("Exec", 1, driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1})
task := runner.RunWorkflow(*workflow) task := runner.RunWorkflow(*workflow)
if !task.Failed() { if !task.Failed() {
@ -69,9 +70,9 @@ jobs:
` `
workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) workflow, _ := workflow.FromYamlBytes([]byte(workflowSample))
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{}))
runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) runner := NewRunner(&mockDriver, map[string]string{"test": "test"})
jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext := context.WithValue(context.Background(), "workflow", *workflow)
@ -97,9 +98,9 @@ jobs:
` `
workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) workflow, _ := workflow.FromYamlBytes([]byte(workflowSample))
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", fmt.Sprintf("%#v", map[string]string{}))
runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) runner := NewRunner(&mockDriver, map[string]string{"test": "test"})
jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext := context.WithValue(context.Background(), "workflow", *workflow)
@ -111,7 +112,7 @@ jobs:
t.Errorf("Did not expect error, got %+v", runErr) t.Errorf("Did not expect error, got %+v", runErr)
} }
execCallCount := len(mockDriver.calls["Exec"]) execCallCount := len(mockDriver.Calls["Exec"])
if execCallCount != 2 { if execCallCount != 2 {
t.Errorf("Expected 2 calls to Exec, got %d", execCallCount) t.Errorf("Expected 2 calls to Exec, got %d", execCallCount)
} }
@ -129,9 +130,9 @@ jobs:
` `
workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) workflow, _ := workflow.FromYamlBytes([]byte(workflowSample))
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{}))
runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) runner := NewRunner(&mockDriver, map[string]string{"test": "test"})
jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext := context.WithValue(context.Background(), "workflow", *workflow)
@ -143,7 +144,7 @@ jobs:
t.Error("Expected error, got nil") t.Error("Expected error, got nil")
} }
execCallCount := len(mockDriver.calls["Exec"]) execCallCount := len(mockDriver.Calls["Exec"])
if execCallCount != 1 { if execCallCount != 1 {
t.Errorf("Expected 1 calls to Exec, got %d", execCallCount) t.Errorf("Expected 1 calls to Exec, got %d", execCallCount)
} }
@ -160,9 +161,9 @@ jobs:
` `
workflow, _ := workflow.FromYamlBytes([]byte(workflowSample)) workflow, _ := workflow.FromYamlBytes([]byte(workflowSample))
mockDriver := NewMockDriver() mockDriver := driver.NewMockDriver()
mockDriver.WithMockedCall("Exec", CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{})) mockDriver.WithMockedCall("Exec", driver.CommandResult{Error: errors.New("exit 1!"), ExitCode: 1}, "testContainer", "exit 1", ".", fmt.Sprintf("%#v", map[string]string{}))
runner := NewRunner(&mockDriver, map[string]string{"test": "test"}) runner := NewRunner(&mockDriver, map[string]string{"test": "test"})
jobContext := context.WithValue(context.Background(), "workflow", *workflow) jobContext := context.WithValue(context.Background(), "workflow", *workflow)
@ -174,7 +175,7 @@ jobs:
t.Error("Expect error, got nil") t.Error("Expect error, got nil")
} }
execCallCount := len(mockDriver.calls["Exec"]) execCallCount := len(mockDriver.Calls["Exec"])
if execCallCount != 1 { if execCallCount != 1 {
t.Errorf("Expected 1 calls to Exec, got %d", execCallCount) t.Errorf("Expected 1 calls to Exec, got %d", execCallCount)
} }