forked from containers/podman
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial quadlet version integrated in golang
Based on the initial port in containers/quadlet#41 Signed-off-by: Alexander Larsson <alexl@redhat.com>
- Loading branch information
1 parent
84aff62
commit 71636ff
Showing
11 changed files
with
3,228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/containers/podman/v4/pkg/quadlet" | ||
"github.com/containers/podman/v4/pkg/systemdparser" | ||
) | ||
|
||
var ( | ||
verboseFlag bool // True if -v passed | ||
isUser bool // True if run as quadlet-user-generator executable | ||
) | ||
|
||
var ( | ||
// data saved between logToKmsg calls | ||
noKmsg = false | ||
kmsgFile *os.File | ||
) | ||
|
||
func logToKmsg(s string) bool { | ||
if noKmsg { | ||
return false | ||
} | ||
|
||
if kmsgFile == nil { | ||
f, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0644) | ||
if err != nil { | ||
noKmsg = true | ||
return false | ||
} | ||
kmsgFile = f | ||
} | ||
|
||
if _, err := kmsgFile.Write([]byte(s)); err != nil { | ||
kmsgFile.Close() | ||
kmsgFile = nil | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func Logf(format string, a ...interface{}) { | ||
s := fmt.Sprintf(format, a...) | ||
line := fmt.Sprintf("quadlet-generator[%d]: %s", os.Getpid(), s) | ||
|
||
if !logToKmsg(line) { | ||
// If we can't log, print to stderr | ||
fmt.Fprintf(os.Stderr, "%s\n", line) | ||
os.Stderr.Sync() | ||
} | ||
} | ||
|
||
var debugEnabled = false | ||
|
||
func enableDebug() { | ||
debugEnabled = true | ||
} | ||
|
||
func Debugf(format string, a ...interface{}) { | ||
if debugEnabled { | ||
Logf(format, a...) | ||
} | ||
} | ||
|
||
func getUnitDirs(user bool) []string { | ||
unitDirsEnv := os.Getenv("QUADLET_UNIT_DIRS") | ||
if len(unitDirsEnv) > 0 { | ||
return strings.Split(unitDirsEnv, ":") | ||
} | ||
|
||
dirs := make([]string, 0) | ||
if user { | ||
if configDir, err := os.UserConfigDir(); err == nil { | ||
dirs = append(dirs, path.Join(configDir, "containers/systemd")) | ||
} | ||
} else { | ||
dirs = append(dirs, quadlet.UnitDirAdmin) | ||
dirs = append(dirs, quadlet.UnitDirDistro) | ||
} | ||
return dirs | ||
} | ||
|
||
func loadUnitsFromDir(sourcePath string, units map[string]*systemdparser.UnitFile) { | ||
files, err := os.ReadDir(sourcePath) | ||
if err != nil { | ||
if !errors.Is(err, os.ErrNotExist) { | ||
Logf("Can't read \"%s\": %s", sourcePath, err) | ||
} | ||
return | ||
} | ||
|
||
for _, file := range files { | ||
name := file.Name() | ||
if units[name] == nil && | ||
(strings.HasSuffix(name, ".container") || | ||
strings.HasSuffix(name, ".volume")) { | ||
path := path.Join(sourcePath, name) | ||
|
||
Debugf("Loading source unit file %s", path) | ||
|
||
if f, err := systemdparser.ParseUnitFile(path); err != nil { | ||
Logf("Error loading '%s', ignoring: %s", path, err) | ||
} else { | ||
units[name] = f | ||
} | ||
} | ||
} | ||
} | ||
|
||
func generateServiceFile(service *systemdparser.UnitFile) error { | ||
Debugf("writing '%s'", service.Path) | ||
|
||
service.PrependComment("", | ||
"Automatically generated by quadlet-generator", | ||
"") | ||
|
||
f, err := os.Create(service.Path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer f.Close() | ||
|
||
err = service.Write(f) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = f.Sync() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func enableServiceFile(outputPath string, service *systemdparser.UnitFile) { | ||
symlinks := make([]string, 0) | ||
|
||
aliases := service.LookupAllStrv(quadlet.InstallGroup, "Alias") | ||
for _, alias := range aliases { | ||
symlinks = append(symlinks, filepath.Clean(alias)) | ||
} | ||
|
||
wantedBy := service.LookupAllStrv(quadlet.InstallGroup, "WantedBy") | ||
for _, wantedByUnit := range wantedBy { | ||
// Only allow filenames, not paths | ||
if !strings.Contains(wantedByUnit, "/") { | ||
symlinks = append(symlinks, fmt.Sprintf("%s.wants/%s", wantedByUnit, service.Filename)) | ||
} | ||
} | ||
|
||
requiredBy := service.LookupAllStrv(quadlet.InstallGroup, "RequiredBy") | ||
for _, requiredByUnit := range requiredBy { | ||
// Only allow filenames, not paths | ||
if !strings.Contains(requiredByUnit, "/") { | ||
symlinks = append(symlinks, fmt.Sprintf("%s.requires/%s", requiredByUnit, service.Filename)) | ||
} | ||
} | ||
|
||
for _, symlinkRel := range symlinks { | ||
target, err := filepath.Rel(path.Dir(symlinkRel), service.Filename) | ||
if err != nil { | ||
Logf("Can't create symlink %s: %s", symlinkRel, err) | ||
continue | ||
} | ||
symlinkPath := path.Join(outputPath, symlinkRel) | ||
|
||
symlinkDir := path.Dir(symlinkPath) | ||
err = os.MkdirAll(symlinkDir, os.ModePerm) | ||
if err != nil { | ||
Logf("Can't create dir %s: %s", symlinkDir, err) | ||
continue | ||
} | ||
|
||
Debugf("Creating symlink %s -> %s", symlinkPath, target) | ||
_ = os.Remove(symlinkPath) // overwrite existing symlinks | ||
err = os.Symlink(target, symlinkPath) | ||
if err != nil { | ||
Logf("Failed creating symlink %s: %s", symlinkPath, err) | ||
} | ||
} | ||
} | ||
|
||
func main() { | ||
prgname := path.Base(os.Args[0]) | ||
isUser = strings.Contains(prgname, "user") | ||
|
||
flag.Parse() | ||
|
||
if verboseFlag { | ||
enableDebug() | ||
} | ||
|
||
if flag.NArg() < 1 { | ||
Logf("Missing output directory argument") | ||
os.Exit(1) | ||
} | ||
|
||
outputPath := flag.Arg(0) | ||
|
||
Debugf("Starting quadlet-generator, output to: %s", outputPath) | ||
|
||
sourcePaths := getUnitDirs(isUser) | ||
|
||
units := make(map[string]*systemdparser.UnitFile) | ||
for _, d := range sourcePaths { | ||
loadUnitsFromDir(d, units) | ||
} | ||
|
||
err := os.MkdirAll(outputPath, os.ModePerm) | ||
if err != nil { | ||
Logf("Can't create dir %s: %s", outputPath, err) | ||
os.Exit(1) | ||
} | ||
|
||
for name, unit := range units { | ||
var service *systemdparser.UnitFile | ||
var err error | ||
|
||
switch { | ||
case strings.HasSuffix(name, ".container"): | ||
service, err = quadlet.ConvertContainer(unit, isUser) | ||
case strings.HasSuffix(name, ".volume"): | ||
service, err = quadlet.ConvertVolume(unit, name) | ||
default: | ||
Logf("Unsupported file type '%s'", name) | ||
continue | ||
} | ||
|
||
if err != nil { | ||
Logf("Error converting '%s', ignoring: %s", name, err) | ||
} else { | ||
service.Path = path.Join(outputPath, service.Filename) | ||
|
||
if err := generateServiceFile(service); err != nil { | ||
Logf("Error writing '%s'o: %s", service.Path, err) | ||
} | ||
enableServiceFile(outputPath, service) | ||
} | ||
} | ||
} | ||
|
||
func init() { | ||
flag.BoolVar(&verboseFlag, "v", false, "Print debug information") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package quadlet | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
) | ||
|
||
/* This is a helper for constructing podman commandlines */ | ||
type PodmanCmdline struct { | ||
Args []string | ||
} | ||
|
||
func (c *PodmanCmdline) add(args ...string) { | ||
c.Args = append(c.Args, args...) | ||
} | ||
|
||
func (c *PodmanCmdline) addf(format string, a ...interface{}) { | ||
c.add(fmt.Sprintf(format, a...)) | ||
} | ||
|
||
func (c *PodmanCmdline) addKeys(arg string, keys map[string]string) { | ||
ks := make([]string, 0, len(keys)) | ||
for k := range keys { | ||
ks = append(ks, k) | ||
} | ||
sort.Strings(ks) | ||
|
||
for _, k := range ks { | ||
c.add(arg, fmt.Sprintf("%s=%s", k, keys[k])) | ||
} | ||
} | ||
|
||
func (c *PodmanCmdline) addEnv(env map[string]string) { | ||
c.addKeys("--env", env) | ||
} | ||
|
||
func (c *PodmanCmdline) addLabels(labels map[string]string) { | ||
c.addKeys("--label", labels) | ||
} | ||
|
||
func (c *PodmanCmdline) addAnnotations(annotations map[string]string) { | ||
c.addKeys("--annotation", annotations) | ||
} | ||
|
||
func (c *PodmanCmdline) addIDMap(argPrefix string, containerIDStart, hostIDStart, numIDs uint32) { | ||
if numIDs != 0 { | ||
c.add(argPrefix) | ||
c.addf("%d:%d:%d", containerIDStart, hostIDStart, numIDs) | ||
} | ||
} | ||
|
||
func NewPodmanCmdline(args ...string) *PodmanCmdline { | ||
c := &PodmanCmdline{ | ||
Args: make([]string, 0), | ||
} | ||
|
||
c.add("/usr/bin/podman") | ||
c.add(args...) | ||
return c | ||
} |
Oops, something went wrong.