From bb6dec46ff8f3123334df27634f9a80fd47703d8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 20 Dec 2023 15:28:25 +0100 Subject: [PATCH] quadlet: Support [Install] for templated units For a base template like `foo@.container` the WantedBy and RequiredBy keys does nothing. However, if a DefaultInstance= key is specified that is used by default. However, even if the DefaultInstance= is not given, the Install section is still useful, because you can instantiate the generic template by making a symlink for it, and that symlink will then pick up the instance id. So, for example, this foo@.container will not enable anything on boot. ``` [Container] Image=foo Exec=sleep 100 [Install] WantedBy=other.container ``` But if you have a symlink 'foo@instance.container` -> `foo@.container' then the `foo@instance` service will be marked as wanted by `other`. In addition, even if the main template doesn't have an Install section, you can instantiate it with a symlink like above, and then enabling it using a dropin file like foo@instance.container.d/install.conf containing: ``` [Install] WantedBy=other.container ``` Signed-off-by: Alexander Larsson --- cmd/quadlet/main.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/cmd/quadlet/main.go b/cmd/quadlet/main.go index 2f1c6387ae..447820dae2 100644 --- a/cmd/quadlet/main.go +++ b/cmd/quadlet/main.go @@ -345,19 +345,36 @@ func enableServiceFile(outputPath string, service *parser.UnitFile) { 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)) + serviceFilename := service.Filename + templateBase, templateInstance := service.GetTemplateParts() + + // For non-instantiated template service we only support installs if a + // DefaultInstance is given. Otherwise we ignore the Install group, but + // it is still useful when instantiating the unit via a symlink. + if templateBase != "" && templateInstance == "" { + if defaultInstance, ok := service.Lookup(quadlet.InstallGroup, "DefaultInstance"); ok { + parts := strings.SplitN(templateBase, "@", 2) + serviceFilename = parts[0] + "@" + defaultInstance + parts[1] + } else { + serviceFilename = "" } } - 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)) + if serviceFilename != "" { + 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, serviceFilename)) + } + } + + 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, serviceFilename)) + } } }