diff options
Diffstat (limited to 'pkg/systemdgen')
-rw-r--r-- | pkg/systemdgen/systemdgen.go | 147 | ||||
-rw-r--r-- | pkg/systemdgen/systemdgen_test.go | 180 |
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) } }) } |