diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/images.go | 4 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/generate.go | 28 | ||||
-rw-r--r-- | pkg/api/server/register_generate.go | 21 | ||||
-rw-r--r-- | pkg/bindings/generate/types.go | 6 | ||||
-rw-r--r-- | pkg/bindings/generate/types_systemd_options.go | 45 | ||||
-rw-r--r-- | pkg/domain/entities/generate.go | 6 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/generate.go | 12 | ||||
-rw-r--r-- | pkg/machine/wsl/machine.go | 163 | ||||
-rw-r--r-- | pkg/machine/wsl/util_windows.go | 19 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 4 | ||||
-rw-r--r-- | pkg/systemd/generate/containers.go | 23 | ||||
-rw-r--r-- | pkg/systemd/generate/containers_test.go | 188 | ||||
-rw-r--r-- | pkg/systemd/generate/pods.go | 20 | ||||
-rw-r--r-- | pkg/systemd/generate/pods_test.go | 203 |
14 files changed, 724 insertions, 18 deletions
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 97fa4ddad..23a9b12a3 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -138,7 +138,9 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { options.Message = query.Comment options.Author = query.Author options.Pause = query.Pause - options.Changes = strings.Fields(query.Changes) + if query.Changes != "" { + options.Changes = strings.Split(query.Changes, ",") + } ctr, err := runtime.LookupContainer(query.Container) if err != nil { utils.Error(w, "Something went wrong.", http.StatusNotFound, err) diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go index aadb5ad52..9b62a1388 100644 --- a/pkg/api/handlers/libpod/generate.go +++ b/pkg/api/handlers/libpod/generate.go @@ -17,17 +17,20 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - Name bool `schema:"useName"` - New bool `schema:"new"` - NoHeader bool `schema:"noHeader"` - TemplateUnitFile bool `schema:"templateUnitFile"` - RestartPolicy *string `schema:"restartPolicy"` - RestartSec uint `schema:"restartSec"` - StopTimeout uint `schema:"stopTimeout"` - StartTimeout uint `schema:"startTimeout"` - ContainerPrefix string `schema:"containerPrefix"` - PodPrefix string `schema:"podPrefix"` - Separator string `schema:"separator"` + Name bool `schema:"useName"` + New bool `schema:"new"` + NoHeader bool `schema:"noHeader"` + TemplateUnitFile bool `schema:"templateUnitFile"` + RestartPolicy *string `schema:"restartPolicy"` + RestartSec uint `schema:"restartSec"` + StopTimeout uint `schema:"stopTimeout"` + StartTimeout uint `schema:"startTimeout"` + ContainerPrefix string `schema:"containerPrefix"` + PodPrefix string `schema:"podPrefix"` + Separator string `schema:"separator"` + Wants []string `schema:"wants"` + After []string `schema:"after"` + Requires []string `schema:"requires"` }{ StartTimeout: 0, StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout, @@ -55,6 +58,9 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { PodPrefix: query.PodPrefix, Separator: query.Separator, RestartSec: &query.RestartSec, + Wants: query.Wants, + After: query.After, + Requires: query.Requires, } report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options) diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 47057959c..6b7f0cfe7 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -72,6 +72,27 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { // type: integer // default: 0 // description: Configures the time to sleep before restarting a service. + // - in: query + // name: wants + // type: array + // items: + // type: string + // default: [] + // description: Systemd Wants list for the container or pods. + // - in: query + // name: after + // type: array + // items: + // type: string + // default: [] + // description: Systemd After list for the container or pods. + // - in: query + // name: requires + // type: array + // items: + // type: string + // default: [] + // description: Systemd Requires list for the container or pods. // produces: // - application/json // responses: diff --git a/pkg/bindings/generate/types.go b/pkg/bindings/generate/types.go index ce560c547..25c398c8b 100644 --- a/pkg/bindings/generate/types.go +++ b/pkg/bindings/generate/types.go @@ -32,4 +32,10 @@ type SystemdOptions struct { PodPrefix *string // Separator - systemd unit name separator between name/id and prefix Separator *string + // Wants - systemd wants list for the container or pods + Wants *[]string + // After - systemd after list for the container or pods + After *[]string + // Requires - systemd requires list for the container or pods + Requires *[]string } diff --git a/pkg/bindings/generate/types_systemd_options.go b/pkg/bindings/generate/types_systemd_options.go index 960e45e50..4d436945b 100644 --- a/pkg/bindings/generate/types_systemd_options.go +++ b/pkg/bindings/generate/types_systemd_options.go @@ -181,3 +181,48 @@ func (o *SystemdOptions) GetSeparator() string { } return *o.Separator } + +// WithWants set field Wants to given value +func (o *SystemdOptions) WithWants(value []string) *SystemdOptions { + o.Wants = &value + return o +} + +// GetWants returns value of field Wants +func (o *SystemdOptions) GetWants() []string { + if o.Wants == nil { + var z []string + return z + } + return *o.Wants +} + +// WithAfter set field After to given value +func (o *SystemdOptions) WithAfter(value []string) *SystemdOptions { + o.After = &value + return o +} + +// GetAfter returns value of field After +func (o *SystemdOptions) GetAfter() []string { + if o.After == nil { + var z []string + return z + } + return *o.After +} + +// WithRequires set field Requires to given value +func (o *SystemdOptions) WithRequires(value []string) *SystemdOptions { + o.Requires = &value + return o +} + +// GetRequires returns value of field Requires +func (o *SystemdOptions) GetRequires() []string { + if o.Requires == nil { + var z []string + return z + } + return *o.Requires +} diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index e431a70af..73dd64ecd 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -26,6 +26,12 @@ type GenerateSystemdOptions struct { NoHeader bool // TemplateUnitFile - make use of %i and %I to differentiate between the different instances of the unit TemplateUnitFile bool + // Wants - systemd wants list for the container or pods + Wants []string + // After - systemd after list for the container or pods + After []string + // Requires - systemd requires list for the container or pods + Requires []string } // GenerateSystemdReport diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index 49b66e908..235d478ec 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -8,7 +8,17 @@ import ( ) func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, opts entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { - options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader).WithTemplateUnitFile(opts.TemplateUnitFile).WithPodPrefix(opts.PodPrefix).WithSeparator(opts.Separator) + options := new( + generate.SystemdOptions). + WithUseName(opts.Name). + WithContainerPrefix(opts.ContainerPrefix). + WithNew(opts.New).WithNoHeader(opts.NoHeader). + WithTemplateUnitFile(opts.TemplateUnitFile). + WithPodPrefix(opts.PodPrefix). + WithSeparator(opts.Separator). + WithWants(opts.Wants). + WithAfter(opts.After). + WithRequires(opts.Requires) if opts.StartTimeout != nil { options.WithStartTimeout(*opts.StartTimeout) diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 6cab855d3..c7d857954 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -1,4 +1,3 @@ -//go:build windows // +build windows package wsl @@ -143,6 +142,11 @@ http://docs.microsoft.com/en-us/windows/wsl/install\ ` +const ( + winSShProxy = "win-sshproxy.exe" + winSshProxyTid = "win-sshproxy.tid" +) + type Provider struct{} type MachineVM struct { @@ -705,8 +709,6 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return errors.Errorf("%q is already running", name) } - fmt.Println("Starting machine...") - dist := toDist(name) err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap") @@ -714,9 +716,107 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return errors.Wrap(err, "WSL bootstrap script failed") } + globalName, pipeName, err := launchWinProxy(v) + if err != nil { + fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.") + fmt.Fprintf(os.Stderr, "\t%s\n", err.Error()) + fmt.Fprintln(os.Stderr, "\nPodman clients are still able to connect.") + } else { + fmt.Printf("API forwarding listening on: %s\n", pipeName) + if globalName { + fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n") + } else { + fmt.Printf("\nAnother process was listening on the default Docker API pipe address.\n") + fmt.Printf("You can still connect Docker API clients by setting DOCKER HOST using the\n") + fmt.Printf("following powershell command in your terminal session:\n") + fmt.Printf("\n\t$Env:DOCKER_HOST = '%s'\n", pipeName) + fmt.Printf("\nOr in a classic CMD prompt:\n") + fmt.Printf("\n\tset DOCKER_HOST = '%s'\n", pipeName) + fmt.Printf("\nAlternatively terminate the other process and restart podman machine.\n") + } + } + return markStart(name) } +func launchWinProxy(v *MachineVM) (bool, string, error) { + globalName := true + pipeName := "docker_engine" + if !pipeAvailable(pipeName) { + pipeName = toDist(v.Name) + globalName = false + if !pipeAvailable(pipeName) { + return globalName, "", errors.Errorf("could not start api proxy since expected pipe is not available: %s", pipeName) + } + } + fullPipeName := "npipe:////./pipe/" + pipeName + + exe, err := os.Executable() + if err != nil { + return globalName, "", err + } + + exe, err = filepath.EvalSymlinks(exe) + if err != nil { + return globalName, "", err + } + + command := filepath.Join(filepath.Dir(exe), winSShProxy) + stateDir, err := getWinProxyStateDir(v) + if err != nil { + return globalName, "", err + } + + dest := fmt.Sprintf("ssh://root@localhost:%d/run/podman/podman.sock", v.Port) + cmd := exec.Command(command, v.Name, stateDir, fullPipeName, dest, v.IdentityPath) + if err := cmd.Start(); err != nil { + return globalName, "", err + } + + return globalName, fullPipeName, waitPipeExists(pipeName, 30, func() error { + active, exitCode := getProcessState(cmd.Process.Pid) + if !active { + return errors.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode) + } + + return nil + }) +} + +func getWinProxyStateDir(v *MachineVM) (string, error) { + dir, err := machine.GetDataDir(vmtype) + if err != nil { + return "", err + } + stateDir := filepath.Join(dir, v.Name) + if err = os.MkdirAll(stateDir, 0755); err != nil { + return "", err + } + + return stateDir, nil +} + +func pipeAvailable(pipeName string) bool { + _, err := os.Stat(`\\.\pipe\` + pipeName) + return os.IsNotExist(err) +} + +func waitPipeExists(pipeName string, retries int, checkFailure func() error) error { + var err error + for i := 0; i < retries; i++ { + _, err = os.Stat(`\\.\pipe\` + pipeName) + if err == nil { + break + } + if fail := checkFailure(); fail != nil { + return fail + } + time.Sleep(100 * time.Millisecond) + } + + return err +} + func isWSLInstalled() bool { cmd := exec.Command("wsl", "--status") out, err := cmd.StdoutPipe() @@ -817,6 +917,10 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return errors.Errorf("%q is not running", v.Name) } + if err := stopWinProxy(v); err != nil { + fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) + } + cmd := exec.Command("wsl", "-d", dist, "sh") cmd.Stdin = strings.NewReader(waitTerm) if err = cmd.Start(); err != nil { @@ -840,6 +944,59 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return nil } +func stopWinProxy(v *MachineVM) error { + pid, tid, tidFile, err := readWinProxyTid(v) + if err != nil { + return err + } + + proc, err := os.FindProcess(int(pid)) + if err != nil { + return nil + } + sendQuit(tid) + _ = waitTimeout(proc, 20*time.Second) + _ = os.Remove(tidFile) + + return nil +} + +func waitTimeout(proc *os.Process, timeout time.Duration) bool { + done := make(chan bool) + go func() { + proc.Wait() + done <- true + }() + ret := false + select { + case <-time.After(timeout): + proc.Kill() + <-done + case <-done: + ret = true + break + } + + return ret +} + +func readWinProxyTid(v *MachineVM) (uint32, uint32, string, error) { + stateDir, err := getWinProxyStateDir(v) + if err != nil { + return 0, 0, "", err + } + + tidFile := filepath.Join(stateDir, winSshProxyTid) + contents, err := ioutil.ReadFile(tidFile) + if err != nil { + return 0, 0, "", err + } + + var pid, tid uint32 + fmt.Sscanf(string(contents), "%d:%d", &pid, &tid) + return pid, tid, tidFile, nil +} + //nolint:cyclop func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) { var files []string diff --git a/pkg/machine/wsl/util_windows.go b/pkg/machine/wsl/util_windows.go index 95e4c9894..b5c28e015 100644 --- a/pkg/machine/wsl/util_windows.go +++ b/pkg/machine/wsl/util_windows.go @@ -67,6 +67,7 @@ const ( TOKEN_QUERY = 0x0008 SE_PRIVILEGE_ENABLED = 0x00000002 SE_ERR_ACCESSDENIED = 0x05 + WM_QUIT = 0x12 ) func winVersionAtLeast(major uint, minor uint, build uint) bool { @@ -279,6 +280,18 @@ func obtainShutdownPrivilege() error { return nil } +func getProcessState(pid int) (active bool, exitCode int) { + const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE + handle, err := syscall.OpenProcess(da, false, uint32(pid)) + if err != nil { + return false, int(syscall.ERROR_PROC_NOT_FOUND) + } + + var code uint32 + syscall.GetExitCodeProcess(handle, &code) + return code == 259, int(code) +} + func addRunOnceRegistryEntry(command string) error { k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE) if err != nil { @@ -336,3 +349,9 @@ func buildCommandArgs(elevate bool) string { } return strings.Join(args, " ") } + +func sendQuit(tid uint32) { + user32 := syscall.NewLazyDLL("user32.dll") + postMessage := user32.NewProc("PostThreadMessageW") + postMessage.Call(uintptr(tid), WM_QUIT, 0, 0) +} diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 22f5041d0..a0b6edcfb 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -390,11 +390,11 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo return joinUserAndMountNS(uint(pid), "") } } - return false, -1, errors.Wrapf(err, "error setting up the process") + return false, -1, errors.New("error setting up the process") } if b[0] != '0' { - return false, -1, errors.Wrapf(err, "error setting up the process") + return false, -1, errors.New("error setting up the process") } signals := []os.Signal{} diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index fd5c247f3..ea829c810 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -94,6 +94,13 @@ type containerInfo struct { RunRoot string // Add %i and %I to description and execute parts IdentifySpecifier bool + // Wants are the list of services that this service is (weak) dependent on. This + // option does not influence the order in which services are started or stopped. + Wants []string + // After ordering dependencies between the list of services and this service. + After []string + // Similar to Wants, but declares a stronger requirement dependency. + Requires []string } const containerTemplate = headerTemplate + ` @@ -101,6 +108,19 @@ const containerTemplate = headerTemplate + ` BindsTo={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}} After={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}} {{{{- end}}}} +{{{{- if or .Wants .After .Requires }}}} + +# User-defined dependencies +{{{{- end}}}} +{{{{- if .Wants}}}} +Wants={{{{- range $index, $value := .Wants }}}}{{{{ if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} +{{{{- if .After}}}} +After={{{{- range $index, $value := .After }}}}{{{{ if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} +{{{{- if .Requires}}}} +Requires={{{{- range $index, $value := .Requires }}}}{{{{ if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} [Service] Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i{{{{- end}}}} @@ -201,6 +221,9 @@ func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSyste CreateCommand: createCommand, RunRoot: runRoot, containerEnv: envs, + Wants: options.Wants, + After: options.After, + Requires: options.Requires, } return &info, nil diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 45bb5173a..2f653a4b9 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -91,6 +91,116 @@ Type=forking WantedBy=default.target ` + goodNameCustomWants := `# container-foobar.service +# autogenerated by Podman CI + +[Unit] +Description=Podman container-foobar.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +# User-defined dependencies +Wants=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStart=/usr/bin/podman start foobar +ExecStop=/usr/bin/podman stop -t 10 foobar +ExecStopPost=/usr/bin/podman stop -t 10 foobar +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + + goodNameCustomAfter := `# container-foobar.service +# autogenerated by Podman CI + +[Unit] +Description=Podman container-foobar.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +# User-defined dependencies +After=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStart=/usr/bin/podman start foobar +ExecStop=/usr/bin/podman stop -t 10 foobar +ExecStopPost=/usr/bin/podman stop -t 10 foobar +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + + goodNameCustomRequires := `# container-foobar.service +# autogenerated by Podman CI + +[Unit] +Description=Podman container-foobar.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +# User-defined dependencies +Requires=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStart=/usr/bin/podman start foobar +ExecStop=/usr/bin/podman stop -t 10 foobar +ExecStopPost=/usr/bin/podman stop -t 10 foobar +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + + goodNameCustomDependencies := `# container-foobar.service +# autogenerated by Podman CI + +[Unit] +Description=Podman container-foobar.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +# User-defined dependencies +Wants=a.service b.service c.target +After=a.service b.service c.target +Requires=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStart=/usr/bin/podman start foobar +ExecStop=/usr/bin/podman stop -t 10 foobar +ExecStopPost=/usr/bin/podman stop -t 10 foobar +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + goodNameBoundTo := `# container-foobar.service # autogenerated by Podman CI @@ -613,6 +723,84 @@ WantedBy=default.target false, false, }, + {"good with name and wants", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "container-foobar", + ContainerNameOrID: "foobar", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + Wants: []string{"a.service", "b.service", "c.target"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + goodNameCustomWants, + false, + false, + false, + false, + }, + {"good with name and after", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "container-foobar", + ContainerNameOrID: "foobar", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + After: []string{"a.service", "b.service", "c.target"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + goodNameCustomAfter, + false, + false, + false, + false, + }, + {"good with name and requires", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "container-foobar", + ContainerNameOrID: "foobar", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + Requires: []string{"a.service", "b.service", "c.target"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + goodNameCustomRequires, + false, + false, + false, + false, + }, + {"good with name and dependencies", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "container-foobar", + ContainerNameOrID: "foobar", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + Wants: []string{"a.service", "b.service", "c.target"}, + After: []string{"a.service", "b.service", "c.target"}, + Requires: []string{"a.service", "b.service", "c.target"}, + EnvVariable: define.EnvVariable, + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + }, + goodNameCustomDependencies, + false, + false, + false, + false, + }, {"good with name and bound to", containerInfo{ Executable: "/usr/bin/podman", diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index 17e1dc5a2..003c23e77 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -83,10 +83,30 @@ type podInfo struct { RunRoot string // Add %i and %I to description and execute parts - this should not be used IdentifySpecifier bool + // Wants are the list of services that this service is (weak) dependent on. This + // option does not influence the order in which services are started or stopped. + Wants []string + // After ordering dependencies between the list of services and this service. + After []string + // Similar to Wants, but declares a stronger requirement dependency. + Requires []string } const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}} Before={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}} +{{{{- if or .Wants .After .Requires }}}} + +# User-defined dependencies +{{{{- end}}}} +{{{{- if .Wants}}}} +Wants={{{{- range $index, $value := .Wants }}}}{{{{ if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} +{{{{- if .After}}}} +After={{{{- range $index, $value := .After }}}}{{{{ if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} +{{{{- if .Requires}}}} +Requires={{{{- range $index, $value := .Requires }}}}{{{{ if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} [Service] Environment={{{{.EnvVariable}}}}=%n diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index 6c84c8895..b37e0825b 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -67,6 +67,121 @@ WantedBy=default.target podGood := serviceInfo + headerInfo + podContent podGoodNoHeaderInfo := serviceInfo + podContent + podGoodCustomWants := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +# User-defined dependencies +Wants=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=102 +ExecStart=/usr/bin/podman start jadda-jadda-infra +ExecStop=/usr/bin/podman stop -t 42 jadda-jadda-infra +ExecStopPost=/usr/bin/podman stop -t 42 jadda-jadda-infra +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + podGoodCustomAfter := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +# User-defined dependencies +After=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=102 +ExecStart=/usr/bin/podman start jadda-jadda-infra +ExecStop=/usr/bin/podman stop -t 42 jadda-jadda-infra +ExecStopPost=/usr/bin/podman stop -t 42 jadda-jadda-infra +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + podGoodCustomRequires := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +# User-defined dependencies +Requires=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=102 +ExecStart=/usr/bin/podman start jadda-jadda-infra +ExecStop=/usr/bin/podman stop -t 42 jadda-jadda-infra +ExecStopPost=/usr/bin/podman stop -t 42 jadda-jadda-infra +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + podGoodCustomDependencies := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +# User-defined dependencies +Wants=a.service b.service c.target +After=a.service b.service c.target +Requires=a.service b.service c.target + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=102 +ExecStart=/usr/bin/podman start jadda-jadda-infra +ExecStop=/usr/bin/podman stop -t 42 jadda-jadda-infra +ExecStopPost=/usr/bin/podman stop -t 42 jadda-jadda-infra +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + podGoodRestartSec := `# pod-123abc.service # autogenerated by Podman CI @@ -232,6 +347,94 @@ WantedBy=default.target false, false, }, + {"pod", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + RequiredServices: []string{"container-1", "container-2"}, + Wants: []string{"a.service", "b.service", "c.target"}, + CreateCommand: []string{ + "podman", "pod", "create", "--name", "foo", "--wants", "a.service", + "--wants", "b.service", "--wants", "c.target", "bar=arg with space"}, + }, + podGoodCustomWants, + false, + false, + false, + }, + {"pod", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + RequiredServices: []string{"container-1", "container-2"}, + After: []string{"a.service", "b.service", "c.target"}, + CreateCommand: []string{ + "podman", "pod", "create", "--name", "foo", "--after", "a.service", + "--after", "b.service", "--after", "c.target", "bar=arg with space"}, + }, + podGoodCustomAfter, + false, + false, + false, + }, + {"pod", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + RequiredServices: []string{"container-1", "container-2"}, + Requires: []string{"a.service", "b.service", "c.target"}, + CreateCommand: []string{ + "podman", "pod", "create", "--name", "foo", "--requires", "a.service", + "--requires", "b.service", "--requires", "c.target", "bar=arg with space"}, + }, + podGoodCustomRequires, + false, + false, + false, + }, + {"pod", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + RequiredServices: []string{"container-1", "container-2"}, + Wants: []string{"a.service", "b.service", "c.target"}, + After: []string{"a.service", "b.service", "c.target"}, + Requires: []string{"a.service", "b.service", "c.target"}, + CreateCommand: []string{ + "podman", "pod", "create", "--name", "foo", "--wants", "a.service", + "--wants", "b.service", "--wants", "c.target", "--after", "a.service", + "--after", "b.service", "--after", "c.target", "--requires", "a.service", + "--requires", "b.service", "--requires", "c.target", "bar=arg with space"}, + }, + podGoodCustomDependencies, + false, + false, + false, + }, {"pod restartSec", podInfo{ Executable: "/usr/bin/podman", |