Quadlet: Add support for .kube files

Get the path to the yaml file and call podman kube play
Add tests

Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
Ygal Blum
2022-11-22 11:53:53 +02:00
parent b570b9d66f
commit 3084ed4686
4 changed files with 133 additions and 9 deletions

View File

@ -34,6 +34,15 @@ var (
kmsgFile *os.File kmsgFile *os.File
) )
var (
void struct{}
supportedExtensions = map[string]struct{}{
".container": void,
".volume": void,
".kube": void,
}
)
// We log directly to /dev/kmsg, because that is the only way to get information out // We log directly to /dev/kmsg, because that is the only way to get information out
// of the generator into the system logs. // of the generator into the system logs.
func logToKmsg(s string) bool { func logToKmsg(s string) bool {
@ -105,6 +114,12 @@ func getUnitDirs(user bool) []string {
return dirs return dirs
} }
func isExtSupported(filename string) bool {
ext := filepath.Ext(filename)
_, ok := supportedExtensions[ext]
return ok
}
func loadUnitsFromDir(sourcePath string, units map[string]*parser.UnitFile) { func loadUnitsFromDir(sourcePath string, units map[string]*parser.UnitFile) {
files, err := os.ReadDir(sourcePath) files, err := os.ReadDir(sourcePath)
if err != nil { if err != nil {
@ -116,9 +131,7 @@ func loadUnitsFromDir(sourcePath string, units map[string]*parser.UnitFile) {
for _, file := range files { for _, file := range files {
name := file.Name() name := file.Name()
if units[name] == nil && if units[name] == nil && isExtSupported(name) {
(strings.HasSuffix(name, ".container") ||
strings.HasSuffix(name, ".volume")) {
path := path.Join(sourcePath, name) path := path.Join(sourcePath, name)
Debugf("Loading source unit file %s", path) Debugf("Loading source unit file %s", path)
@ -322,6 +335,8 @@ func main() {
service, err = quadlet.ConvertContainer(unit, isUser) service, err = quadlet.ConvertContainer(unit, isUser)
case strings.HasSuffix(name, ".volume"): case strings.HasSuffix(name, ".volume"):
service, err = quadlet.ConvertVolume(unit, name) service, err = quadlet.ConvertVolume(unit, name)
case strings.HasSuffix(name, ".kube"):
service, err = quadlet.ConvertKube(unit)
default: default:
Logf("Unsupported file type '%s'", name) Logf("Unsupported file type '%s'", name)
continue continue

View File

@ -22,6 +22,8 @@ const (
XContainerGroup = "X-Container" XContainerGroup = "X-Container"
VolumeGroup = "Volume" VolumeGroup = "Volume"
XVolumeGroup = "X-Volume" XVolumeGroup = "X-Volume"
KubeGroup = "Kube"
XKubeGroup = "X-Kube"
) )
var validPortRange = regexp.MustCompile(`\d+(-\d+)?(/udp|/tcp)?$`) var validPortRange = regexp.MustCompile(`\d+(-\d+)?(/udp|/tcp)?$`)
@ -55,6 +57,7 @@ const (
KeySeccompProfile = "SeccompProfile" KeySeccompProfile = "SeccompProfile"
KeyAddDevice = "AddDevice" KeyAddDevice = "AddDevice"
KeyNetwork = "Network" KeyNetwork = "Network"
KeyYaml = "Yaml"
) )
// Supported keys in "Container" group // Supported keys in "Container" group
@ -95,6 +98,11 @@ var supportedVolumeKeys = map[string]bool{
KeyLabel: true, KeyLabel: true,
} }
// Supported keys in "Kube" group
var supportedKubeKeys = map[string]bool{
KeyYaml: true,
}
func replaceExtension(name string, extension string, extraPrefix string, extraSuffix string) string { func replaceExtension(name string, extension string, extraPrefix string, extraSuffix string) string {
baseName := name baseName := name
@ -593,3 +601,65 @@ func ConvertVolume(volume *parser.UnitFile, name string) (*parser.UnitFile, erro
return service, nil return service, nil
} }
func ConvertKube(kube *parser.UnitFile) (*parser.UnitFile, error) {
service := kube.Dup()
service.Filename = replaceExtension(kube.Filename, ".service", "", "")
if kube.Path != "" {
service.Add(UnitGroup, "SourcePath", kube.Path)
}
if err := checkForUnknownKeys(kube, KubeGroup, supportedKubeKeys); err != nil {
return nil, err
}
// Rename old Kube group to x-Kube so that systemd ignores it
service.RenameGroup(KubeGroup, XKubeGroup)
yamlPath, ok := kube.Lookup(KubeGroup, KeyYaml)
if !ok || len(yamlPath) == 0 {
return nil, fmt.Errorf("no Yaml key specified")
}
// Only allow mixed or control-group, as nothing else works well
killMode, ok := service.Lookup(ServiceGroup, "KillMode")
if !ok || !(killMode == "mixed" || killMode == "control-group") {
if ok {
return nil, fmt.Errorf("invalid KillMode '%s'", killMode)
}
// We default to mixed instead of control-group, because it lets conmon do its thing
service.Set(ServiceGroup, "KillMode", "mixed")
}
// Set PODMAN_SYSTEMD_UNIT so that podman auto-update can restart the service.
service.Add(ServiceGroup, "Environment", "PODMAN_SYSTEMD_UNIT=%n")
// Need the containers filesystem mounted to start podman
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
service.Setv(ServiceGroup,
"Type", "notify",
"NotifyAccess", "all")
execStart := NewPodmanCmdline("kube", "play")
execStart.add(
// Replace any previous container with the same name, not fail
"--replace",
// Use a service container
"--service-container=true",
)
execStart.add(yamlPath)
service.AddCmdline(ServiceGroup, "ExecStart", execStart.Args)
execStop := NewPodmanCmdline("kube", "down")
execStop.add(yamlPath)
service.AddCmdline(ServiceGroup, "ExecStop", execStop.Args)
return service, nil
}

View File

@ -0,0 +1,17 @@
## assert-podman-args "kube"
## assert-podman-args "play"
## assert-podman-final-args deployment.yml
## assert-podman-args "--replace"
## assert-podman-args "--service-container=true"
## assert-podman-stop-args "kube"
## assert-podman-stop-args "down"
## assert-podman-stop-final-args deployment.yml
## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
## assert-key-is "Service" "KillMode" "mixed"
## assert-key-is "Service" "Type" "notify"
## assert-key-is "Service" "NotifyAccess" "all"
## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n"
[Kube]
Yaml=deployment.yml

View File

@ -110,19 +110,35 @@ func (t *quadletTestcase) assertKeyContains(args []string, unit *parser.UnitFile
return ok && strings.Contains(realValue, value) return ok && strings.Contains(realValue, value)
} }
func (t *quadletTestcase) assertPodmanArgs(args []string, unit *parser.UnitFile) bool { func (t *quadletTestcase) assertPodmanArgs(args []string, unit *parser.UnitFile, key string) bool {
podmanArgs, _ := unit.LookupLastArgs("Service", "ExecStart") podmanArgs, _ := unit.LookupLastArgs("Service", key)
return findSublist(podmanArgs, args) != -1 return findSublist(podmanArgs, args) != -1
} }
func (t *quadletTestcase) assertFinalArgs(args []string, unit *parser.UnitFile) bool { func (t *quadletTestcase) assertPodmanFinalArgs(args []string, unit *parser.UnitFile, key string) bool {
podmanArgs, _ := unit.LookupLastArgs("Service", "ExecStart") podmanArgs, _ := unit.LookupLastArgs("Service", key)
if len(podmanArgs) < len(args) { if len(podmanArgs) < len(args) {
return false return false
} }
return matchSublistAt(podmanArgs, len(podmanArgs)-len(args), args) return matchSublistAt(podmanArgs, len(podmanArgs)-len(args), args)
} }
func (t *quadletTestcase) assertStartPodmanArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStart")
}
func (t *quadletTestcase) assertStartPodmanFinalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgs(args, unit, "ExecStart")
}
func (t *quadletTestcase) assertStopPodmanArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStop")
}
func (t *quadletTestcase) assertStopPodmanFinalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgs(args, unit, "ExecStop")
}
func (t *quadletTestcase) assertSymlink(args []string, unit *parser.UnitFile) bool { func (t *quadletTestcase) assertSymlink(args []string, unit *parser.UnitFile) bool {
symlink := args[0] symlink := args[0]
expectedTarget := args[1] expectedTarget := args[1]
@ -161,11 +177,15 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
case "assert-key-contains": case "assert-key-contains":
ok = t.assertKeyContains(args, unit) ok = t.assertKeyContains(args, unit)
case "assert-podman-args": case "assert-podman-args":
ok = t.assertPodmanArgs(args, unit) ok = t.assertStartPodmanArgs(args, unit)
case "assert-podman-final-args": case "assert-podman-final-args":
ok = t.assertFinalArgs(args, unit) ok = t.assertStartPodmanFinalArgs(args, unit)
case "assert-symlink": case "assert-symlink":
ok = t.assertSymlink(args, unit) ok = t.assertSymlink(args, unit)
case "assert-podman-stop-args":
ok = t.assertStopPodmanArgs(args, unit)
case "assert-podman-stop-final-args":
ok = t.assertStopPodmanFinalArgs(args, unit)
default: default:
return fmt.Errorf("Unsupported assertion %s", op) return fmt.Errorf("Unsupported assertion %s", op)
} }
@ -300,6 +320,8 @@ var _ = Describe("quadlet system generator", func() {
Entry("basic.volume", "basic.volume"), Entry("basic.volume", "basic.volume"),
Entry("label.volume", "label.volume"), Entry("label.volume", "label.volume"),
Entry("uid.volume", "uid.volume"), Entry("uid.volume", "uid.volume"),
Entry("Basic kube", "basic.kube"),
) )
}) })