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)).
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
logger "courgette/internal/logging"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
@ -22,8 +24,21 @@ func (d PodmanDriver) Pull(uri string) error {
|
|||
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 {
|
||||
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.Stderr = os.Stderr
|
||||
|
||||
|
@ -34,8 +49,26 @@ func (d PodmanDriver) Start(uri string, containerName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Stops a container and removes any volumes labelled with its name
|
||||
// as owner.
|
||||
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.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