summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/domain/infra/abi/generate.go158
-rw-r--r--pkg/systemd/generate/common.go36
-rw-r--r--pkg/systemd/generate/containers.go109
-rw-r--r--pkg/systemd/generate/generate_test.go (renamed from pkg/systemd/generate/containers_test.go)182
-rw-r--r--pkg/systemd/generate/pods.go130
5 files changed, 333 insertions, 282 deletions
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
index 08cb87287..fa0cfb389 100644
--- a/pkg/domain/infra/abi/generate.go
+++ b/pkg/domain/infra/abi/generate.go
@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"fmt"
- "strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
@@ -16,165 +15,28 @@ import (
)
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
- opts := generate.Options{
- Files: options.Files,
- New: options.New,
- }
-
// First assume it's a container.
- if info, found, err := ic.generateSystemdgenContainerInfo(nameOrID, nil, options); found && err != nil {
- return nil, err
- } else if found && err == nil {
- output, err := generate.CreateContainerSystemdUnit(info, opts)
- if err != nil {
- return nil, err
+ ctr, err := ic.Libpod.LookupContainer(nameOrID)
+ if err == nil {
+ // Generate the unit for the container.
+ s, err := generate.ContainerUnit(ctr, options)
+ if err == nil {
+ return &entities.GenerateSystemdReport{Output: s}, nil
}
- return &entities.GenerateSystemdReport{Output: output}, nil
- }
-
- // --new does not support pods.
- if options.New {
- return nil, errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
}
- // We're either having a pod or garbage.
+ // If it's not a container, we either have a pod or garbage.
pod, err := ic.Libpod.LookupPod(nameOrID)
if err != nil {
return nil, errors.Errorf("%q does not refer to a container or pod", nameOrID)
}
- // Error out if the pod has no infra container, which we require to be the
- // main service.
- if !pod.HasInfraContainer() {
- return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
- }
-
- // Generate a systemdgen.ContainerInfo for the infra container. This
- // ContainerInfo acts as the main service of the pod.
- infraID, err := pod.InfraContainerID()
- if err != nil {
- return nil, nil
- }
- podInfo, _, err := ic.generateSystemdgenContainerInfo(infraID, pod, options)
+ // Generate the units for the pod and all its containers.
+ s, err := generate.PodUnits(pod, options)
if err != nil {
return nil, err
}
-
- // Compute the container-dependency graph for the Pod.
- containers, err := pod.AllContainers()
- if err != nil {
- return nil, err
- }
- if len(containers) == 0 {
- return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
- }
- graph, err := libpod.BuildContainerGraph(containers)
- if err != nil {
- return nil, err
- }
-
- // Traverse the dependency graph and create systemdgen.ContainerInfo's for
- // each container.
- containerInfos := []*generate.ContainerInfo{podInfo}
- for ctr, dependencies := range graph.DependencyMap() {
- // Skip the infra container as we already generated it.
- if ctr.ID() == infraID {
- continue
- }
- ctrInfo, _, err := ic.generateSystemdgenContainerInfo(ctr.ID(), nil, options)
- if err != nil {
- return nil, err
- }
- // Now add the container's dependencies and at the container as a
- // required service of the infra container.
- for _, dep := range dependencies {
- if dep.ID() == infraID {
- ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
- } else {
- _, serviceName := generateServiceName(dep, nil, options)
- ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
- }
- }
- podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
- containerInfos = append(containerInfos, ctrInfo)
- }
-
- // Now generate the systemd service for all containers.
- builder := strings.Builder{}
- for i, info := range containerInfos {
- if i > 0 {
- builder.WriteByte('\n')
- }
- out, err := generate.CreateContainerSystemdUnit(info, opts)
- if err != nil {
- return nil, err
- }
- builder.WriteString(out)
- }
-
- return &entities.GenerateSystemdReport{Output: builder.String()}, nil
-}
-
-// generateSystemdgenContainerInfo is a helper to generate a
-// systemdgen.ContainerInfo for `GenerateSystemd`.
-func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod *libpod.Pod, options entities.GenerateSystemdOptions) (*generate.ContainerInfo, bool, error) {
- ctr, err := ic.Libpod.LookupContainer(nameOrID)
- if err != nil {
- return nil, false, err
- }
-
- timeout := ctr.StopTimeout()
- if options.StopTimeout != nil {
- timeout = *options.StopTimeout
- }
-
- config := ctr.Config()
- conmonPidFile := config.ConmonPidFile
- if conmonPidFile == "" {
- return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
- }
-
- createCommand := []string{}
- if config.CreateCommand != nil {
- createCommand = config.CreateCommand
- } else if options.New {
- return nil, true, errors.Errorf("cannot use --new on container %q: no create command found", nameOrID)
- }
-
- name, serviceName := generateServiceName(ctr, pod, options)
- info := &generate.ContainerInfo{
- ServiceName: serviceName,
- ContainerName: name,
- RestartPolicy: options.RestartPolicy,
- PIDFile: conmonPidFile,
- StopTimeout: timeout,
- GenerateTimestamp: true,
- CreateCommand: createCommand,
- }
-
- return info, true, nil
-}
-
-// generateServiceName generates the container name and the service name for systemd service.
-func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) {
- var kind, name, ctrName string
- if pod == nil {
- kind = options.ContainerPrefix //defaults to container
- name = ctr.ID()
- if options.Name {
- name = ctr.Name()
- }
- ctrName = name
- } else {
- kind = options.PodPrefix //defaults to pod
- name = pod.ID()
- ctrName = ctr.ID()
- if options.Name {
- name = pod.Name()
- ctrName = ctr.Name()
- }
- }
- return ctrName, fmt.Sprintf("%s%s%s", kind, options.Separator, name)
+ return &entities.GenerateSystemdReport{Output: s}, nil
}
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go
new file mode 100644
index 000000000..e809b4837
--- /dev/null
+++ b/pkg/systemd/generate/common.go
@@ -0,0 +1,36 @@
+package generate
+
+import (
+ "github.com/pkg/errors"
+)
+
+// 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"
+
+// restartPolicies includes all valid restart policies to be used in a unit
+// file.
+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 headerTemplate = `# {{.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
+`
diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go
index fb0ea5cf9..f316d4452 100644
--- a/pkg/systemd/generate/containers.go
+++ b/pkg/systemd/generate/containers.go
@@ -11,22 +11,20 @@ import (
"text/template"
"time"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
"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
+// containerInfo contains data required for generating a container's systemd
// unit file.
-type ContainerInfo struct {
+type containerInfo struct {
// ServiceName of the systemd service.
ServiceName string
// Name or ID of the container.
- ContainerName string
+ ContainerNameOrID string
// StopTimeout sets the timeout Podman waits before killing the container
// during service stop.
StopTimeout uint
@@ -63,29 +61,7 @@ type ContainerInfo struct {
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
+const containerTemplate = headerTemplate + `
{{- if .BoundToServices}}
RefuseManualStart=yes
RefuseManualStop=yes
@@ -107,8 +83,8 @@ ExecStop={{.Executable}} stop --ignore --cidfile %t/%n-ctr-id {{if (ge .StopTime
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}}
+ExecStart={{.Executable}} start {{.ContainerNameOrID}}
+ExecStop={{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerNameOrID}}
PIDFile={{.PIDFile}}
{{- end}}
KillMode=none
@@ -117,18 +93,19 @@ 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
+// ContainerUnit generates a systemd unit for the specified container. Based
+// on the options, the return value might be the entire unit or a file it has
+// been written to.
+func ContainerUnit(ctr *libpod.Container, options entities.GenerateSystemdOptions) (string, error) {
+ info, err := generateContainerInfo(ctr, options)
+ if err != nil {
+ return "", err
+ }
+ return createContainerSystemdUnit(info, options)
}
-// CreateContainerSystemdUnit creates a systemd unit file for a container.
-func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, error) {
+// createContainerSystemdUnit creates a systemd unit file for a container.
+func createContainerSystemdUnit(info *containerInfo, options entities.GenerateSystemdOptions) (string, error) {
if err := validateRestartPolicy(info.RestartPolicy); err != nil {
return "", err
}
@@ -152,7 +129,7 @@ func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, erro
// 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 {
+ if options.New {
// The create command must at least have three arguments:
// /usr/bin/podman run $IMAGE
index := 2
@@ -218,7 +195,7 @@ func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, erro
return "", err
}
- if !opts.Files {
+ if !options.Files {
return buf.String(), nil
}
@@ -233,3 +210,47 @@ func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, erro
}
return path, nil
}
+
+func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSystemdOptions) (*containerInfo, error) {
+ timeout := ctr.StopTimeout()
+ if options.StopTimeout != nil {
+ timeout = *options.StopTimeout
+ }
+
+ config := ctr.Config()
+ conmonPidFile := config.ConmonPidFile
+ if conmonPidFile == "" {
+ return nil, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ }
+
+ createCommand := []string{}
+ if config.CreateCommand != nil {
+ createCommand = config.CreateCommand
+ } else if options.New {
+ return nil, errors.Errorf("cannot use --new on container %q: no create command found", ctr.ID())
+ }
+
+ nameOrID, serviceName := containerServiceName(ctr, options)
+
+ info := containerInfo{
+ ServiceName: serviceName,
+ ContainerNameOrID: nameOrID,
+ RestartPolicy: options.RestartPolicy,
+ PIDFile: conmonPidFile,
+ StopTimeout: timeout,
+ GenerateTimestamp: true,
+ CreateCommand: createCommand,
+ }
+ return &info, nil
+}
+
+// containerServiceName returns the nameOrID and the service name of the
+// container.
+func containerServiceName(ctr *libpod.Container, options entities.GenerateSystemdOptions) (string, string) {
+ nameOrID := ctr.ID()
+ if options.Name {
+ nameOrID = ctr.Name()
+ }
+ serviceName := fmt.Sprintf("%s%s%s", options.ContainerPrefix, options.Separator, nameOrID)
+ return nameOrID, serviceName
+}
diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/generate_test.go
index 9da261a69..11cabb463 100644
--- a/pkg/systemd/generate/containers_test.go
+++ b/pkg/systemd/generate/generate_test.go
@@ -2,31 +2,33 @@ package generate
import (
"testing"
+
+ "github.com/containers/libpod/pkg/domain/entities"
)
func TestValidateRestartPolicy(t *testing.T) {
- type ContainerInfo struct {
+ type containerInfo struct {
restart string
}
tests := []struct {
name string
- ContainerInfo ContainerInfo
+ containerInfo containerInfo
wantErr bool
}{
- {"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},
+ {"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 {
test := tt
t.Run(tt.name, func(t *testing.T) {
- if err := validateRestartPolicy(test.ContainerInfo.restart); (err != nil) != test.wantErr {
+ if err := validateRestartPolicy(test.containerInfo.restart); (err != nil) != test.wantErr {
t.Errorf("ValidateRestartPolicy() error = %v, wantErr %v", err, test.wantErr)
}
})
@@ -195,67 +197,67 @@ WantedBy=multi-user.target default.target`
tests := []struct {
name string
- info ContainerInfo
+ info containerInfo
want string
wantErr bool
}{
{"good with id",
- 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",
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ ContainerNameOrID: "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
},
goodID,
false,
},
{"good with name",
- 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",
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-foobar",
+ ContainerNameOrID: "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"},
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-foobar",
+ ContainerNameOrID: "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"},
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "pod-123abc",
+ ContainerNameOrID: "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",
- ContainerInfo{
+ containerInfo{
Executable: "/usr/bin/podman",
ServiceName: "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
RestartPolicy: "never",
@@ -267,61 +269,61 @@ WantedBy=multi-user.target default.target`
true,
},
{"good with name and generic",
- ContainerInfo{
- Executable: "/usr/bin/podman",
- ServiceName: "jadda-jadda",
- ContainerName: "jadda-jadda",
- RestartPolicy: "always",
- PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
- StopTimeout: 42,
- PodmanVersion: "CI",
- New: true,
- CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "jadda-jadda",
+ ContainerNameOrID: "jadda-jadda",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 42,
+ PodmanVersion: "CI",
+ New: true,
+ CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
},
goodNameNew,
false,
},
{"good with explicit short detach param",
- ContainerInfo{
- Executable: "/usr/bin/podman",
- ServiceName: "jadda-jadda",
- ContainerName: "jadda-jadda",
- RestartPolicy: "always",
- PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
- StopTimeout: 42,
- PodmanVersion: "CI",
- New: true,
- CreateCommand: []string{"I'll get stripped", "container", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "jadda-jadda",
+ ContainerNameOrID: "jadda-jadda",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 42,
+ PodmanVersion: "CI",
+ New: true,
+ CreateCommand: []string{"I'll get stripped", "container", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
},
goodNameNew,
false,
},
{"good with explicit full detach param",
- ContainerInfo{
- Executable: "/usr/bin/podman",
- ServiceName: "jadda-jadda",
- ContainerName: "jadda-jadda",
- RestartPolicy: "always",
- PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
- StopTimeout: 42,
- PodmanVersion: "CI",
- New: true,
- CreateCommand: []string{"I'll get stripped", "container", "run", "--detach", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "jadda-jadda",
+ ContainerNameOrID: "jadda-jadda",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 42,
+ PodmanVersion: "CI",
+ New: true,
+ CreateCommand: []string{"I'll get stripped", "container", "run", "--detach", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
},
goodNameNewDetach,
false,
},
{"good with id and no param",
- 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",
- New: true,
- CreateCommand: []string{"I'll get stripped", "container", "run", "awesome-image:latest"},
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ ContainerNameOrID: "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
+ RestartPolicy: "always",
+ PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
+ New: true,
+ CreateCommand: []string{"I'll get stripped", "container", "run", "awesome-image:latest"},
},
goodIDNew,
false,
@@ -330,11 +332,11 @@ WantedBy=multi-user.target default.target`
for _, tt := range tests {
test := tt
t.Run(tt.name, func(t *testing.T) {
- opts := Options{
+ opts := entities.GenerateSystemdOptions{
Files: false,
New: test.info.New,
}
- got, err := CreateContainerSystemdUnit(&test.info, opts)
+ got, err := createContainerSystemdUnit(&test.info, opts)
if (err != nil) != test.wantErr {
t.Errorf("CreateContainerSystemdUnit() error = \n%v, wantErr \n%v", err, test.wantErr)
return
diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go
new file mode 100644
index 000000000..16f324f78
--- /dev/null
+++ b/pkg/systemd/generate/pods.go
@@ -0,0 +1,130 @@
+package generate
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// PodUnits generates systemd units for the specified pod and its containers.
+// Based on the options, the return value might be the content of all units or
+// the files they been written to.
+func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, error) {
+ if options.New {
+ return "", errors.New("--new is not supported for pods")
+ }
+
+ // Error out if the pod has no infra container, which we require to be the
+ // main service.
+ if !pod.HasInfraContainer() {
+ return "", fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
+ }
+
+ podInfo, err := generatePodInfo(pod, options)
+ if err != nil {
+ return "", err
+ }
+
+ infraID, err := pod.InfraContainerID()
+ if err != nil {
+ return "", err
+ }
+
+ // Compute the container-dependency graph for the Pod.
+ containers, err := pod.AllContainers()
+ if err != nil {
+ return "", err
+ }
+ if len(containers) == 0 {
+ return "", fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
+ }
+ graph, err := libpod.BuildContainerGraph(containers)
+ if err != nil {
+ return "", err
+ }
+
+ // Traverse the dependency graph and create systemdgen.containerInfo's for
+ // each container.
+ containerInfos := []*containerInfo{podInfo}
+ for ctr, dependencies := range graph.DependencyMap() {
+ // Skip the infra container as we already generated it.
+ if ctr.ID() == infraID {
+ continue
+ }
+ ctrInfo, err := generateContainerInfo(ctr, options)
+ if err != nil {
+ return "", err
+ }
+ // Now add the container's dependencies and at the container as a
+ // required service of the infra container.
+ for _, dep := range dependencies {
+ if dep.ID() == infraID {
+ ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
+ } else {
+ _, serviceName := containerServiceName(dep, options)
+ ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
+ }
+ }
+ podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
+ containerInfos = append(containerInfos, ctrInfo)
+ }
+
+ // Now generate the systemd service for all containers.
+ builder := strings.Builder{}
+ for i, info := range containerInfos {
+ if i > 0 {
+ builder.WriteByte('\n')
+ }
+ out, err := createContainerSystemdUnit(info, options)
+ if err != nil {
+ return "", err
+ }
+ builder.WriteString(out)
+ }
+
+ return builder.String(), nil
+}
+
+func generatePodInfo(pod *libpod.Pod, options entities.GenerateSystemdOptions) (*containerInfo, error) {
+ // Generate a systemdgen.containerInfo for the infra container. This
+ // containerInfo acts as the main service of the pod.
+ infraCtr, err := pod.InfraContainer()
+ if err != nil {
+ return nil, errors.Wrap(err, "could not find infra container")
+ }
+
+ timeout := infraCtr.StopTimeout()
+ if options.StopTimeout != nil {
+ timeout = *options.StopTimeout
+ }
+
+ config := infraCtr.Config()
+ conmonPidFile := config.ConmonPidFile
+ if conmonPidFile == "" {
+ return nil, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ }
+
+ createCommand := []string{}
+
+ nameOrID := pod.ID()
+ ctrNameOrID := infraCtr.ID()
+ if options.Name {
+ nameOrID = pod.Name()
+ ctrNameOrID = infraCtr.Name()
+ }
+ serviceName := fmt.Sprintf("%s%s%s", options.PodPrefix, options.Separator, nameOrID)
+
+ info := containerInfo{
+ ServiceName: serviceName,
+ ContainerNameOrID: ctrNameOrID,
+ RestartPolicy: options.RestartPolicy,
+ PIDFile: conmonPidFile,
+ StopTimeout: timeout,
+ GenerateTimestamp: true,
+ CreateCommand: createCommand,
+ }
+ return &info, nil
+}