summaryrefslogtreecommitdiff
path: root/pkg/systemdgen/systemdgen.go
blob: 09d3c6fd5228c9d7e1a17b24c6bbda36c9734056 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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"
)

// 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 {
	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)
{{- 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`

// 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
	}

	// 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))
	}

	// 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 !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
}