feat: create, mount and cleanup volumes for job containers
This commit is contained in:
parent
c352b05d6c
commit
a974c36c74
3 changed files with 209 additions and 2 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
168
internal/runner/podman_driver_test.go
Normal file
168
internal/runner/podman_driver_test.go
Normal 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.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue