summaryrefslogtreecommitdiff
path: root/pkg/systemdgen
diff options
context:
space:
mode:
authorValentin Rothberg <rothberg@redhat.com>2019-08-13 13:06:37 +0200
committerValentin Rothberg <rothberg@redhat.com>2019-08-21 17:28:30 +0200
commit56a65cffac2cee3132c950d49ea8a5b46eabbff1 (patch)
treec7353580fc33d03c05ade690717980c72630b691 /pkg/systemdgen
parenta33e4a89ca4f689096f417ebddfe5b992470e54d (diff)
downloadpodman-56a65cffac2cee3132c950d49ea8a5b46eabbff1.tar.gz
podman-56a65cffac2cee3132c950d49ea8a5b46eabbff1.tar.bz2
podman-56a65cffac2cee3132c950d49ea8a5b46eabbff1.zip
generate systemd: support pods and geneartig files
Support generating systemd unit files for a pod. Podman generates one unit file for the pod including the PID file for the infra container's conmon process and one unit file for each container (excluding the infra container). Note that this change implies refactorings in the `pkg/systemdgen` API. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
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)
}
})
}