summaryrefslogtreecommitdiff
path: root/pkg/systemd/generate/containers.go
diff options
context:
space:
mode:
authorValentin Rothberg <rothberg@redhat.com>2020-06-05 15:00:57 +0200
committerValentin Rothberg <rothberg@redhat.com>2020-06-11 11:01:13 +0200
commit557a9c12111f8bebaf1466bc267ef8cb992d1c19 (patch)
tree292064ca252bf42544b5e45513ee02fbf924c118 /pkg/systemd/generate/containers.go
parent9232d4e44365e0fa344fc5432dfd4843cb4c200c (diff)
downloadpodman-557a9c12111f8bebaf1466bc267ef8cb992d1c19.tar.gz
podman-557a9c12111f8bebaf1466bc267ef8cb992d1c19.tar.bz2
podman-557a9c12111f8bebaf1466bc267ef8cb992d1c19.zip
generate systemd: rename source files
Rename to `containers{_test}.go` to make some place for the upcoming pod changes. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
Diffstat (limited to 'pkg/systemd/generate/containers.go')
-rw-r--r--pkg/systemd/generate/containers.go235
1 files changed, 235 insertions, 0 deletions
diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go
new file mode 100644
index 000000000..fb0ea5cf9
--- /dev/null
+++ b/pkg/systemd/generate/containers.go
@@ -0,0 +1,235 @@
+package generate
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "text/template"
+ "time"
+
+ "github.com/containers/libpod/version"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// EnvVariable "PODMAN_SYSTEMD_UNIT" is set in all generated systemd units and
+// is set to the unit's (unique) name.
+const EnvVariable = "PODMAN_SYSTEMD_UNIT"
+
+// 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
+ // StopTimeout sets the timeout Podman waits before killing the container
+ // during service stop.
+ StopTimeout uint
+ // 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
+ // New controls if a new container is created or if an existing one is started.
+ New bool
+ // CreateCommand is the full command plus arguments of the process the
+ // container has been created with.
+ CreateCommand []string
+ // RunCommand is a post-processed variant of CreateCommand and used for
+ // the ExecStart field in generic unit files.
+ RunCommand string
+ // EnvVariable is generate.EnvVariable and must not be set.
+ EnvVariable 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 {
+ for _, i := range restartPolicies {
+ if i == restart {
+ return nil
+ }
+ }
+ return errors.Errorf("%s is not a valid restart policy", restart)
+}
+
+const containerTemplate = `# {{.ServiceName}}.service
+# autogenerated by Podman {{.PodmanVersion}}
+{{- if .TimeStamp}}
+# {{.TimeStamp}}
+{{- end}}
+
+[Unit]
+Description=Podman {{.ServiceName}}.service
+Documentation=man:podman-generate-systemd(1)
+Wants=network.target
+After=network-online.target
+{{- 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]
+Environment={{.EnvVariable}}=%n
+Restart={{.RestartPolicy}}
+{{- if .New}}
+ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-ctr-id
+ExecStart={{.RunCommand}}
+ExecStop={{.Executable}} stop --ignore --cidfile %t/%n-ctr-id {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}
+ExecStopPost={{.Executable}} rm --ignore -f --cidfile %t/%n-ctr-id
+PIDFile=%t/%n-pid
+{{- else}}
+ExecStart={{.Executable}} start {{.ContainerName}}
+ExecStop={{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerName}}
+PIDFile={{.PIDFile}}
+{{- end}}
+KillMode=none
+Type=forking
+
+[Install]
+WantedBy=multi-user.target default.target`
+
+// Options include different options to control the unit file generation.
+type Options struct {
+ // When set, generate service files in the current working directory and
+ // return the paths to these files instead of returning all contents in one
+ // big string.
+ Files bool
+ // New controls if a new container is created or if an existing one is started.
+ New bool
+}
+
+// CreateContainerSystemdUnit creates a systemd unit file for a container.
+func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, error) {
+ if err := validateRestartPolicy(info.RestartPolicy); err != nil {
+ return "", err
+ }
+
+ // 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
+ }
+
+ info.EnvVariable = EnvVariable
+
+ // Assemble the ExecStart command when creating a new container.
+ //
+ // Note that we cannot catch all corner cases here such that users
+ // *must* manually check the generated files. A container might have
+ // been created via a Python script, which would certainly yield an
+ // invalid `info.CreateCommand`. Hence, we're doing a best effort unit
+ // generation and don't try aiming at completeness.
+ if opts.New {
+ // The create command must at least have three arguments:
+ // /usr/bin/podman run $IMAGE
+ index := 2
+ if info.CreateCommand[1] == "container" {
+ index = 3
+ }
+ if len(info.CreateCommand) < index+1 {
+ return "", errors.Errorf("container's create command is too short or invalid: %v", info.CreateCommand)
+ }
+ // We're hard-coding the first five arguments and append the
+ // CreateCommand with a stripped command and subcomand.
+ command := []string{
+ info.Executable,
+ "run",
+ "--conmon-pidfile", "%t/%n-pid",
+ "--cidfile", "%t/%n-ctr-id",
+ "--cgroups=no-conmon",
+ }
+
+ // Enforce detaching
+ //
+ // since we use systemd `Type=forking` service
+ // @see https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
+ // when we generated systemd service file with the --new param,
+ // `ExecStart` will have `/usr/bin/podman run ...`
+ // if `info.CreateCommand` has no `-d` or `--detach` param,
+ // podman will run the container in default attached mode,
+ // as a result, `systemd start` will wait the `podman run` command exit until failed with timeout error.
+ hasDetachParam := false
+ for _, p := range info.CreateCommand[index:] {
+ if p == "--detach" || p == "-d" {
+ hasDetachParam = true
+ }
+ }
+ if !hasDetachParam {
+ command = append(command, "-d")
+ }
+
+ command = append(command, info.CreateCommand[index:]...)
+ info.RunCommand = strings.Join(command, " ")
+ info.New = true
+ }
+
+ if info.PodmanVersion == "" {
+ info.PodmanVersion = version.Version
+ }
+ if info.GenerateTimestamp {
+ info.TimeStamp = fmt.Sprintf("%v", time.Now().Format(time.UnixDate))
+ }
+
+ // 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 {
+ return "", errors.Wrap(err, "error parsing systemd service template")
+ }
+
+ var buf bytes.Buffer
+ if err := templ.Execute(&buf, info); err != nil {
+ return "", err
+ }
+
+ if !opts.Files {
+ 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
+}