summaryrefslogtreecommitdiff
path: root/pkg/systemdgen
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/systemdgen')
-rw-r--r--pkg/systemdgen/systemdgen.go147
-rw-r--r--pkg/systemdgen/systemdgen_test.go180
2 files changed, 246 insertions, 81 deletions
diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go
index 06c5ebde5..09d3c6fd5 100644
--- a/pkg/systemdgen/systemdgen.go
+++ b/pkg/systemdgen/systemdgen.go
@@ -1,29 +1,59 @@
package systemdgen
import (
+ "bytes"
"fmt"
+ "io/ioutil"
"os"
+ "path/filepath"
+ "sort"
+ "text/template"
+ "time"
+ "github.com/containers/libpod/version"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-var template = `[Unit]
-Description=%s Podman Container
-[Service]
-Restart=%s
-ExecStart=%s start %s
-ExecStop=%s stop -t %d %s
-KillMode=none
-Type=forking
-PIDFile=%s
-[Install]
-WantedBy=multi-user.target`
+// ContainerInfo contains data required for generating a container's systemd
+// unit file.
+type ContainerInfo struct {
+ // ServiceName of the systemd service.
+ ServiceName string
+ // Name or ID of the container.
+ ContainerName string
+ // InfraContainer of the pod.
+ InfraContainer string
+ // StopTimeout sets the timeout Podman waits before killing the container
+ // during service stop.
+ StopTimeout int
+ // RestartPolicy of the systemd unit (e.g., no, on-failure, always).
+ RestartPolicy string
+ // PIDFile of the service. Required for forking services. Must point to the
+ // PID of the associated conmon process.
+ PIDFile string
+ // GenerateTimestamp, if set the generated unit file has a time stamp.
+ GenerateTimestamp bool
+ // BoundToServices are the services this service binds to. Note that this
+ // service runs after them.
+ BoundToServices []string
+ // RequiredServices are services this service requires. Note that this
+ // service runs before them.
+ RequiredServices []string
+ // PodmanVersion for the header. Will be set internally. Will be auto-filled
+ // if left empty.
+ PodmanVersion string
+ // Executable is the path to the podman executable. Will be auto-filled if
+ // left empty.
+ Executable string
+ // TimeStamp at the time of creating the unit file. Will be set internally.
+ TimeStamp string
+}
var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"}
-// ValidateRestartPolicy checks that the user-provided policy is valid
-func ValidateRestartPolicy(restart string) error {
+// validateRestartPolicy checks that the user-provided policy is valid.
+func validateRestartPolicy(restart string) error {
for _, i := range restartPolicies {
if i == restart {
return nil
@@ -32,28 +62,87 @@ func ValidateRestartPolicy(restart string) error {
return errors.Errorf("%s is not a valid restart policy", restart)
}
-// CreateSystemdUnitAsString takes variables to create a systemd unit file used to control
-// a libpod container
-func CreateSystemdUnitAsString(name, cid, restart, pidFile string, stopTimeout int) (string, error) {
- podmanExe := getPodmanExecutable()
- return createSystemdUnitAsString(podmanExe, name, cid, restart, pidFile, stopTimeout)
-}
+const containerTemplate = `# {{.ServiceName}}.service
+# autogenerated by Podman {{.PodmanVersion}}
+{{- if .TimeStamp}}
+# {{.TimeStamp}}
+{{- end}}
+
+[Unit]
+Description=Podman {{.ServiceName}}.service
+Documentation=man:podman-generate-systemd(1)
+{{- if .BoundToServices}}
+RefuseManualStart=yes
+RefuseManualStop=yes
+BindsTo={{- range $index, $value := .BoundToServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
+After={{- range $index, $value := .BoundToServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
+{{- end}}
+{{- if .RequiredServices}}
+Requires={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
+Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
+{{- end}}
+
+[Service]
+Restart={{.RestartPolicy}}
+ExecStart={{.Executable}} start {{.ContainerName}}
+ExecStop={{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerName}}
+KillMode=none
+Type=forking
+PIDFile={{.PIDFile}}
+
+[Install]
+WantedBy=multi-user.target`
-func createSystemdUnitAsString(exe, name, cid, restart, pidFile string, stopTimeout int) (string, error) {
- if err := ValidateRestartPolicy(restart); err != nil {
+// CreateContainerSystemdUnit creates a systemd unit file for a container.
+func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string, error) {
+ if err := validateRestartPolicy(info.RestartPolicy); err != nil {
return "", err
}
- unit := fmt.Sprintf(template, name, restart, exe, name, exe, stopTimeout, name, pidFile)
- return unit, nil
-}
+ // Make sure the executable is set.
+ if info.Executable == "" {
+ executable, err := os.Executable()
+ if err != nil {
+ executable = "/usr/bin/podman"
+ logrus.Warnf("Could not obtain podman executable location, using default %s", executable)
+ }
+ info.Executable = executable
+ }
+
+ if info.PodmanVersion == "" {
+ info.PodmanVersion = version.Version
+ }
+ if info.GenerateTimestamp {
+ info.TimeStamp = fmt.Sprintf("%v", time.Now().Format(time.UnixDate))
+ }
-func getPodmanExecutable() string {
- podmanExe, err := os.Executable()
+ // Sort the slices to assure a deterministic output.
+ sort.Strings(info.RequiredServices)
+ sort.Strings(info.BoundToServices)
+
+ // Generate the template and compile it.
+ templ, err := template.New("systemd_service_file").Parse(containerTemplate)
if err != nil {
- podmanExe = "/usr/bin/podman"
- logrus.Warnf("Could not obtain podman executable location, using default %s", podmanExe)
+ return "", errors.Wrap(err, "error parsing systemd service template")
}
- return podmanExe
+ var buf bytes.Buffer
+ if err := templ.Execute(&buf, info); err != nil {
+ return "", err
+ }
+
+ if !generateFiles {
+ return buf.String(), nil
+ }
+
+ buf.WriteByte('\n')
+ cwd, err := os.Getwd()
+ if err != nil {
+ return "", errors.Wrap(err, "error getting current working directory")
+ }
+ path := filepath.Join(cwd, fmt.Sprintf("%s.service", info.ServiceName))
+ if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil {
+ return "", errors.Wrap(err, "error generating systemd unit")
+ }
+ return path, nil
}
diff --git a/pkg/systemdgen/systemdgen_test.go b/pkg/systemdgen/systemdgen_test.go
index e413b24ce..1ddb0c514 100644
--- a/pkg/systemdgen/systemdgen_test.go
+++ b/pkg/systemdgen/systemdgen_test.go
@@ -5,36 +5,41 @@ import (
)
func TestValidateRestartPolicy(t *testing.T) {
- type args struct {
+ type ContainerInfo struct {
restart string
}
tests := []struct {
- name string
- args args
- wantErr bool
+ name string
+ ContainerInfo ContainerInfo
+ wantErr bool
}{
- {"good-on", args{restart: "no"}, false},
- {"good-on-success", args{restart: "on-success"}, false},
- {"good-on-failure", args{restart: "on-failure"}, false},
- {"good-on-abnormal", args{restart: "on-abnormal"}, false},
- {"good-on-watchdog", args{restart: "on-watchdog"}, false},
- {"good-on-abort", args{restart: "on-abort"}, false},
- {"good-always", args{restart: "always"}, false},
- {"fail", args{restart: "foobar"}, true},
- {"failblank", args{restart: ""}, true},
+ {"good-on", ContainerInfo{restart: "no"}, false},
+ {"good-on-success", ContainerInfo{restart: "on-success"}, false},
+ {"good-on-failure", ContainerInfo{restart: "on-failure"}, false},
+ {"good-on-abnormal", ContainerInfo{restart: "on-abnormal"}, false},
+ {"good-on-watchdog", ContainerInfo{restart: "on-watchdog"}, false},
+ {"good-on-abort", ContainerInfo{restart: "on-abort"}, false},
+ {"good-always", ContainerInfo{restart: "always"}, false},
+ {"fail", ContainerInfo{restart: "foobar"}, true},
+ {"failblank", ContainerInfo{restart: ""}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := ValidateRestartPolicy(tt.args.restart); (err != nil) != tt.wantErr {
+ if err := validateRestartPolicy(tt.ContainerInfo.restart); (err != nil) != tt.wantErr {
t.Errorf("ValidateRestartPolicy() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
-func TestCreateSystemdUnitAsString(t *testing.T) {
- goodID := `[Unit]
-Description=639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 Podman Container
+func TestCreateContainerSystemdUnit(t *testing.T) {
+ goodID := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service
+Documentation=man:podman-generate-systemd(1)
+
[Service]
Restart=always
ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
@@ -42,11 +47,17 @@ ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+
[Install]
WantedBy=multi-user.target`
- goodName := `[Unit]
-Description=foobar Podman Container
+ goodName := `# container-foobar.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman container-foobar.service
+Documentation=man:podman-generate-systemd(1)
+
[Service]
Restart=always
ExecStart=/usr/bin/podman start foobar
@@ -54,56 +65,121 @@ ExecStop=/usr/bin/podman stop -t 10 foobar
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+
+[Install]
+WantedBy=multi-user.target`
+
+ goodNameBoundTo := `# container-foobar.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman container-foobar.service
+Documentation=man:podman-generate-systemd(1)
+RefuseManualStart=yes
+RefuseManualStop=yes
+BindsTo=a.service b.service c.service pod.service
+After=a.service b.service c.service pod.service
+
+[Service]
+Restart=always
+ExecStart=/usr/bin/podman start foobar
+ExecStop=/usr/bin/podman stop -t 10 foobar
+KillMode=none
+Type=forking
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+
+[Install]
+WantedBy=multi-user.target`
+
+ podGoodName := `# pod-123abc.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman pod-123abc.service
+Documentation=man:podman-generate-systemd(1)
+Requires=container-1.service container-2.service
+Before=container-1.service container-2.service
+
+[Service]
+Restart=always
+ExecStart=/usr/bin/podman start jadda-jadda-infra
+ExecStop=/usr/bin/podman stop -t 10 jadda-jadda-infra
+KillMode=none
+Type=forking
+PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
+
[Install]
WantedBy=multi-user.target`
- type args struct {
- exe string
- name string
- cid string
- restart string
- pidFile string
- stopTimeout int
- }
tests := []struct {
name string
- args args
+ info ContainerInfo
want string
wantErr bool
}{
{"good with id",
- args{
- "/usr/bin/podman",
- "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
- "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
- "always",
- "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
- 10,
+ ContainerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ ContainerName: "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
},
goodID,
false,
},
{"good with name",
- args{
- "/usr/bin/podman",
- "foobar",
- "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
- "always",
- "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
- 10,
+ ContainerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-foobar",
+ ContainerName: "foobar",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
},
goodName,
false,
},
+ {"good with name and bound to",
+ ContainerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-foobar",
+ ContainerName: "foobar",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
+ BoundToServices: []string{"pod", "a", "b", "c"},
+ },
+ goodNameBoundTo,
+ false,
+ },
+ {"pod",
+ ContainerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "pod-123abc",
+ ContainerName: "jadda-jadda-infra",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
+ RequiredServices: []string{"container-1", "container-2"},
+ },
+ podGoodName,
+ false,
+ },
{"bad restart policy",
- args{
- "/usr/bin/podman",
- "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
- "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
- "never",
- "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
- 10,
+ ContainerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ RestartPolicy: "never",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
},
"",
true,
@@ -111,13 +187,13 @@ WantedBy=multi-user.target`
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := createSystemdUnitAsString(tt.args.exe, tt.args.name, tt.args.cid, tt.args.restart, tt.args.pidFile, tt.args.stopTimeout)
+ got, err := CreateContainerSystemdUnit(&tt.info, false)
if (err != nil) != tt.wantErr {
- t.Errorf("CreateSystemdUnitAsString() error = %v, wantErr %v", err, tt.wantErr)
+ t.Errorf("CreateContainerSystemdUnit() error = \n%v, wantErr \n%v", err, tt.wantErr)
return
}
if got != tt.want {
- t.Errorf("CreateSystemdUnitAsString() = %v, want %v", got, tt.want)
+ t.Errorf("CreateContainerSystemdUnit() = \n%v, want \n%v", got, tt.want)
}
})
}