feat: create, mount and cleanup volumes for job containers

This commit is contained in:
Marc 2024-08-11 23:23:41 -04:00
parent c352b05d6c
commit a974c36c74
Signed by: marc
GPG key ID: 048E042F22B5DC79
3 changed files with 209 additions and 2 deletions

View file

@ -10,3 +10,9 @@ The first goal of this project is to reach feature parity with Github Actions's
tracker](./WORKFLOW_SUPPORT.md)). tracker](./WORKFLOW_SUPPORT.md)).
Once this is achieved, extensions to the format will be considered to enhance the design and fix design flaws of the original spec. Once this is achieved, extensions to the format will be considered to enhance the design and fix design flaws of the original spec.
## Testing
Certain tests are gated behind environment flags due to their dependencies (skipped by default):
- Defining `PODMAN_TESTS=1` will include Podman-dependent integration tests for the Podman driver.

View file

@ -4,6 +4,8 @@
package runner package runner
import ( import (
logger "courgette/internal/logging"
"fmt"
"os" "os"
"os/exec" "os/exec"
) )
@ -22,8 +24,21 @@ func (d PodmanDriver) Pull(uri string) error {
return nil return nil
} }
// Sets up a new container with an attached workspace volume.
//
// The volume created is labeled with `owner=<containerName>` so it
// can be easily collected and cleaned up on stop.
func (d PodmanDriver) Start(uri string, containerName string) error { func (d PodmanDriver) Start(uri string, containerName string) error {
cmd := exec.Command("podman", "run", "-td", "--name", containerName, uri) volumeName := fmt.Sprintf("%s-workspace", containerName)
cmd := exec.Command("podman", "volume", "create", volumeName, "--label", fmt.Sprintf("owner=%s", containerName))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
cmd = exec.Command("podman", "run", "-td", "--name", containerName, "-v", fmt.Sprintf("%s:/workspace", volumeName), uri)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@ -34,8 +49,26 @@ func (d PodmanDriver) Start(uri string, containerName string) error {
return nil return nil
} }
// Stops a container and removes any volumes labelled with its name
// as owner.
func (d PodmanDriver) Stop(containerName string) error { func (d PodmanDriver) Stop(containerName string) error {
cmd := exec.Command("podman", "rm", "-f", containerName) cmd := exec.Command("podman", "rm", "-f", "-t", "2", containerName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
cmd = exec.Command("podman", "wait", containerName, "--ignore")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
logger.Error("%+v", err)
}
cmd = exec.Command("podman", "volume", "prune", "--filter", fmt.Sprintf("label=owner=%s", containerName), "-f")
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr

View file

@ -0,0 +1,168 @@
package runner
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"testing"
)
const TEST_IMAGE_NAME = "busybox:stable"
type PodmanMountDetails struct {
Name string `json:"Name"`
Destination string `json:"Destination"`
}
type PodmanInspect struct {
Name string `json:"Name"`
ImageName string `json:"ImageName"`
Mounts []PodmanMountDetails `json:"Mounts"`
}
func MustPodman(t *testing.T, testCase func()) {
cmd := exec.Command("podman", "--version")
err := cmd.Run()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
t.Error("Podman is required for Podman driver tests.")
}
if os.Getenv("PODMAN_TESTS") != "1" {
t.Log("Skipping because PODMAN_TESTS is not 1.")
return
}
testCase()
}
func CleanupContainer(name string) {
exec.Command("podman", "rm", "-f", "-t", "1", name).Run()
}
func CleanupVolume(name string) {
exec.Command("podman", "volume", "rm", "-f", name).Run()
}
func CleanAll(name string) {
CleanupContainer(name)
CleanupVolume(fmt.Sprintf("%s-workspace", name))
}
func ContainerExists(name string) bool {
exists := exec.Command("podman", "container", "exists", name)
exists.Run()
exitCode := exists.ProcessState.ExitCode()
return exitCode == 0
}
func VolumeExists(name string) bool {
exists := exec.Command("podman", "volume", "exists", name)
exists.Run()
exitCode := exists.ProcessState.ExitCode()
return exitCode == 0
}
func InspectContainer(name string) PodmanInspect {
inspect := exec.Command("podman", "inspect", name)
out, _ := inspect.Output()
var inspectOut []PodmanInspect
json.Unmarshal(out, &inspectOut)
return inspectOut[0]
}
func TestStartCreatesAContainerWithTheDesiredUriAndName(t *testing.T) {
MustPodman(t, func() {
driver := PodmanDriver{}
containerName := "test-container"
defer CleanAll(containerName)
driver.Start(TEST_IMAGE_NAME, containerName)
inspectOut := InspectContainer(containerName)
if inspectOut.Name != containerName {
t.Errorf("Expected container name to be %s, got %s instead.", containerName, inspectOut.Name)
}
if !strings.HasSuffix(inspectOut.ImageName, TEST_IMAGE_NAME) {
t.Errorf("Expected image name to be %s, got %s instead.", TEST_IMAGE_NAME, inspectOut.ImageName)
}
})
}
func TestStartCreatesAContainerWithAnAttachedVolume(t *testing.T) {
MustPodman(t, func() {
driver := PodmanDriver{}
containerName := "test-container"
defer CleanAll(containerName)
driver.Start(TEST_IMAGE_NAME, containerName)
inspectOut := InspectContainer(containerName)
if len(inspectOut.Mounts) != 1 {
t.Error("Expected a mount.")
}
expectedVolumeName := fmt.Sprintf("%s-workspace", containerName)
if !VolumeExists(expectedVolumeName) {
t.Error("Expected volume.")
}
if inspectOut.Mounts[0].Name != expectedVolumeName {
t.Errorf("Expected volume name to be %s, got %s instead.", expectedVolumeName, inspectOut.Mounts[0].Name)
}
if inspectOut.Mounts[0].Destination != "/workspace" {
t.Errorf("Expected volume mountpoint to be /workspace, got %s instead.", inspectOut.Mounts[0].Destination)
}
})
}
func TestStopStopsAContainerByName(t *testing.T) {
MustPodman(t, func() {
driver := PodmanDriver{}
containerName := "test-container"
defer CleanAll(containerName)
driver.Start(TEST_IMAGE_NAME, containerName)
driver.Stop(containerName)
if ContainerExists(containerName) {
t.Error("Expected container to be cleaned up, still exists.")
}
})
}
func TestStopCleansUpWorkspaceVolume(t *testing.T) {
MustPodman(t, func() {
driver := PodmanDriver{}
containerName := "test-container"
defer CleanAll(containerName)
driver.Start(TEST_IMAGE_NAME, containerName)
driver.Stop(containerName)
if VolumeExists(fmt.Sprintf("%s-workspace", containerName)) {
t.Error("Expected volume to be cleaned up, still exists.")
}
})
}