summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/cliconfig/config.go1
-rw-r--r--cmd/podman/generate_systemd.go10
-rw-r--r--docs/podman-generate-systemd.1.md66
-rw-r--r--docs/podman-generate.1.md8
-rw-r--r--libpod/container_graph.go24
-rw-r--r--libpod/container_graph_test.go26
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/pod_api.go4
-rw-r--r--pkg/adapter/containers.go137
-rw-r--r--pkg/systemdgen/systemdgen.go147
-rw-r--r--pkg/systemdgen/systemdgen_test.go180
-rw-r--r--test/e2e/generate_systemd_test.go110
12 files changed, 573 insertions, 142 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index e7ad921da..9b5633516 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -156,6 +156,7 @@ type GenerateKubeValues struct {
type GenerateSystemdValues struct {
PodmanCommand
Name bool
+ Files bool
RestartPolicy string
StopTimeout int
}
diff --git a/cmd/podman/generate_systemd.go b/cmd/podman/generate_systemd.go
index 222fc4c98..aa202a68d 100644
--- a/cmd/podman/generate_systemd.go
+++ b/cmd/podman/generate_systemd.go
@@ -5,7 +5,6 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
- "github.com/containers/libpod/pkg/systemdgen"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -40,7 +39,10 @@ func init() {
containerSystemdCommand.SetHelpTemplate(HelpTemplate())
containerSystemdCommand.SetUsageTemplate(UsageTemplate())
flags := containerSystemdCommand.Flags()
- flags.BoolVarP(&containerSystemdCommand.Name, "name", "n", false, "use the container name instead of ID")
+ flags.BoolVarP(&containerSystemdCommand.Name, "name", "n", false, "use the container/pod name instead of ID")
+ if !remoteclient {
+ flags.BoolVarP(&containerSystemdCommand.Files, "files", "f", false, "generate files instead of printing to stdout")
+ }
flags.IntVarP(&containerSystemdCommand.StopTimeout, "timeout", "t", -1, "stop timeout override")
flags.StringVar(&containerSystemdCommand.RestartPolicy, "restart-policy", "on-failure", "applicable systemd restart-policy")
}
@@ -56,10 +58,6 @@ func generateSystemdCmd(c *cliconfig.GenerateSystemdValues) error {
if c.Flag("timeout").Changed && c.StopTimeout < 0 {
return errors.New("timeout value must be 0 or greater")
}
- // Make sure the input restart policy is valid
- if err := systemdgen.ValidateRestartPolicy(c.RestartPolicy); err != nil {
- return err
- }
unit, err := runtime.GenerateSystemd(c)
if err != nil {
diff --git a/docs/podman-generate-systemd.1.md b/docs/podman-generate-systemd.1.md
index ec4bacd09..b4962f28b 100644
--- a/docs/podman-generate-systemd.1.md
+++ b/docs/podman-generate-systemd.1.md
@@ -4,17 +4,20 @@
podman-generate-systemd- Generate Systemd Unit file
## SYNOPSIS
-**podman generate systemd** [*options*] *container*
+**podman generate systemd** [*options*] *container|pod*
## DESCRIPTION
-**podman generate systemd** will create a Systemd unit file that can be used to control a container. The
-command will dynamically create the unit file and output it to stdout where it can be piped by the user
-to a file. The options can be used to influence the results of the output as well.
+**podman generate systemd** will create a systemd unit file that can be used to control a container or pod.
+By default, the command will print the content of the unit files to stdout.
Note that this command is not supported for the remote client.
## OPTIONS:
+**--files**, **-f**
+
+Generate files instead of printing to stdout. The generated files are named {container,pod}-{ID,name}.service and will be placed in the current working directory.
+
**--name**, **-n**
Use the name of the container for the start, stop, and description in the unit file
@@ -28,41 +31,66 @@ Set the systemd restart policy. The restart-policy must be one of: "no", "on-su
"on-watchdog", "on-abort", or "always". The default policy is *on-failure*.
## Examples
-Create a systemd unit file for a container running nginx:
+Create and print a systemd unit file for a container running nginx with an *always* restart policy and 1-second timeout to stdout.
```
-$ sudo podman generate systemd nginx
+$ podman create --name nginx nginx:latest
+$ podman generate systemd --restart-policy=always -t 1 nginx
+# container-de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6.service
+# autogenerated by Podman 1.5.2
+# Wed Aug 21 09:46:45 CEST 2019
+
[Unit]
-Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container
+Description=Podman container-de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6.service
+Documentation=man:podman-generate-systemd(1)
+
[Service]
-Restart=on-failure
-ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
-ExecStop=/usr/bin/podman stop -t 10 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+Restart=always
+ExecStart=/usr/bin/podman start de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6
+ExecStop=/usr/bin/podman stop -t 1 de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6
KillMode=none
Type=forking
-PIDFile=/var/run/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/conmon.pid
+PIDFile=/run/user/1000/overlay-containers/de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6/userdata/conmon.pid
+
[Install]
WantedBy=multi-user.target
```
-Create a systemd unit file for a container running nginx with an *always* restart policy and 1-second timeout.
+Create systemd unit files for a pod with two simple alpine containers. Note that these container services cannot be started or stopped individually via `systemctl`; they are managed by the pod service. You can still use `systemctl status` or journalctl to examine them.
```
-$ sudo podman generate systemd --restart-policy=always -t 1 nginx
+$ podman pod create --name systemd-pod
+$ podman create --pod systemd-pod alpine top
+$ podman create --pod systemd-pod alpine top
+$ podman generate systemd --files --name systemd-pod
+/home/user/pod-systemd-pod.service
+/home/user/container-amazing_chandrasekhar.service
+/home/user/container-jolly_shtern.service
+$ cat pod-systemd-pod.service
+# pod-systemd-pod.service
+# autogenerated by Podman 1.5.2
+# Wed Aug 21 09:52:37 CEST 2019
+
[Unit]
-Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container
+Description=Podman pod-systemd-pod.service
+Documentation=man:podman-generate-systemd(1)
+Requires=container-amazing_chandrasekhar.service container-jolly_shtern.service
+Before=container-amazing_chandrasekhar.service container-jolly_shtern.service
+
[Service]
-Restart=always
-ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
-ExecStop=/usr/bin/podman stop -t 1 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc
+Restart=on-failure
+ExecStart=/usr/bin/podman start 77a818221650-infra
+ExecStop=/usr/bin/podman stop -t 10 77a818221650-infra
KillMode=none
Type=forking
-PIDFile=/var/run/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/conmon.pid
+PIDFile=/run/user/1000/overlay-containers/ccfd5c71a088768774ca7bd05888d55cc287698dde06f475c8b02f696a25adcd/userdata/conmon.pid
+
[Install]
WantedBy=multi-user.target
```
## SEE ALSO
-podman(1), podman-container(1)
+podman(1), podman-container(1), systemctl(1), systemd.unit(5), systemd.service(5)
## HISTORY
+August 2019, Updated with pod support by Valentin Rothberg (rothberg at redhat dot com)
April 2019, Originally compiled by Brent Baude (bbaude at redhat dot com)
diff --git a/docs/podman-generate.1.md b/docs/podman-generate.1.md
index 3f2e43024..50050f2c1 100644
--- a/docs/podman-generate.1.md
+++ b/docs/podman-generate.1.md
@@ -11,10 +11,10 @@ The generate command will create structured output (like YAML) based on a contai
## COMMANDS
-| Command | Man Page | Description |
-|---------|------------------------------------------------------------|------------------------------------------------------------------------------------|
-| kube | [podman-generate-kube(1)](podman-generate-kube.1.md) | Generate Kubernetes YAML based on a pod or container. |
-| systemd | [podman-generate-systemd(1)](podman-generate-systemd.1.md) | Generate a systemd unit file for a container. Not supported for the remote client. |
+| Command | Man Page | Description |
+|---------|------------------------------------------------------------|-------------------------------------------------------------------------------------|
+| kube | [podman-generate-kube(1)](podman-generate-kube.1.md) | Generate Kubernetes YAML based on a pod or container. |
+| systemd | [podman-generate-systemd(1)](podman-generate-systemd.1.md) | Generate systemd unit file(s) for a container. Not supported for the remote client. |
## SEE ALSO
diff --git a/libpod/container_graph.go b/libpod/container_graph.go
index 5aa51bc2f..f6988e1ac 100644
--- a/libpod/container_graph.go
+++ b/libpod/container_graph.go
@@ -16,14 +16,30 @@ type containerNode struct {
dependedOn []*containerNode
}
-type containerGraph struct {
+// ContainerGraph is a dependency graph based on a set of containers.
+type ContainerGraph struct {
nodes map[string]*containerNode
noDepNodes []*containerNode
notDependedOnNodes map[string]*containerNode
}
-func buildContainerGraph(ctrs []*Container) (*containerGraph, error) {
- graph := new(containerGraph)
+// DependencyMap returns the dependency graph as map with the key being a
+// container and the value being the containers the key depends on.
+func (cg *ContainerGraph) DependencyMap() (dependencies map[*Container][]*Container) {
+ dependencies = make(map[*Container][]*Container)
+ for _, node := range cg.nodes {
+ dependsOn := make([]*Container, len(node.dependsOn))
+ for i, d := range node.dependsOn {
+ dependsOn[i] = d.container
+ }
+ dependencies[node.container] = dependsOn
+ }
+ return dependencies
+}
+
+// BuildContainerGraph builds a dependency graph based on the container slice.
+func BuildContainerGraph(ctrs []*Container) (*ContainerGraph, error) {
+ graph := new(ContainerGraph)
graph.nodes = make(map[string]*containerNode)
graph.notDependedOnNodes = make(map[string]*containerNode)
@@ -78,7 +94,7 @@ func buildContainerGraph(ctrs []*Container) (*containerGraph, error) {
// Detect cycles in a container graph using Tarjan's strongly connected
// components algorithm
// Return true if a cycle is found, false otherwise
-func detectCycles(graph *containerGraph) (bool, error) {
+func detectCycles(graph *ContainerGraph) (bool, error) {
type nodeInfo struct {
index int
lowLink int
diff --git a/libpod/container_graph_test.go b/libpod/container_graph_test.go
index d1a52658d..38f03c59c 100644
--- a/libpod/container_graph_test.go
+++ b/libpod/container_graph_test.go
@@ -8,7 +8,7 @@ import (
)
func TestBuildContainerGraphNoCtrsIsEmpty(t *testing.T) {
- graph, err := buildContainerGraph([]*Container{})
+ graph, err := BuildContainerGraph([]*Container{})
assert.NoError(t, err)
assert.Equal(t, 0, len(graph.nodes))
assert.Equal(t, 0, len(graph.noDepNodes))
@@ -24,7 +24,7 @@ func TestBuildContainerGraphOneCtr(t *testing.T) {
ctr1, err := getTestCtr1(manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1})
+ graph, err := BuildContainerGraph([]*Container{ctr1})
assert.NoError(t, err)
assert.Equal(t, 1, len(graph.nodes))
assert.Equal(t, 1, len(graph.noDepNodes))
@@ -49,7 +49,7 @@ func TestBuildContainerGraphTwoCtrNoEdge(t *testing.T) {
ctr2, err := getTestCtr2(manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2})
assert.NoError(t, err)
assert.Equal(t, 2, len(graph.nodes))
assert.Equal(t, 2, len(graph.noDepNodes))
@@ -76,7 +76,7 @@ func TestBuildContainerGraphTwoCtrOneEdge(t *testing.T) {
assert.NoError(t, err)
ctr2.config.UserNsCtr = ctr1.config.ID
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2})
assert.NoError(t, err)
assert.Equal(t, 2, len(graph.nodes))
assert.Equal(t, 1, len(graph.noDepNodes))
@@ -99,7 +99,7 @@ func TestBuildContainerGraphTwoCtrCycle(t *testing.T) {
ctr2.config.UserNsCtr = ctr1.config.ID
ctr1.config.NetNsCtr = ctr2.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2})
assert.Error(t, err)
}
@@ -116,7 +116,7 @@ func TestBuildContainerGraphThreeCtrNoEdges(t *testing.T) {
ctr3, err := getTestCtrN("3", manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.NoError(t, err)
assert.Equal(t, 3, len(graph.nodes))
assert.Equal(t, 3, len(graph.noDepNodes))
@@ -150,7 +150,7 @@ func TestBuildContainerGraphThreeContainersTwoInCycle(t *testing.T) {
ctr1.config.UserNsCtr = ctr2.config.ID
ctr2.config.IPCNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.Error(t, err)
}
@@ -170,7 +170,7 @@ func TestBuildContainerGraphThreeContainersCycle(t *testing.T) {
ctr2.config.IPCNsCtr = ctr3.config.ID
ctr3.config.NetNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.Error(t, err)
}
@@ -190,7 +190,7 @@ func TestBuildContainerGraphThreeContainersNoCycle(t *testing.T) {
ctr1.config.NetNsCtr = ctr3.config.ID
ctr2.config.IPCNsCtr = ctr3.config.ID
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.NoError(t, err)
assert.Equal(t, 3, len(graph.nodes))
assert.Equal(t, 1, len(graph.noDepNodes))
@@ -215,7 +215,7 @@ func TestBuildContainerGraphFourContainersNoEdges(t *testing.T) {
ctr4, err := getTestCtrN("4", manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.NoError(t, err)
assert.Equal(t, 4, len(graph.nodes))
assert.Equal(t, 4, len(graph.noDepNodes))
@@ -256,7 +256,7 @@ func TestBuildContainerGraphFourContainersTwoInCycle(t *testing.T) {
ctr1.config.IPCNsCtr = ctr2.config.ID
ctr2.config.UserNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.Error(t, err)
}
@@ -280,7 +280,7 @@ func TestBuildContainerGraphFourContainersAllInCycle(t *testing.T) {
ctr3.config.NetNsCtr = ctr4.config.ID
ctr4.config.UTSNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.Error(t, err)
}
@@ -303,7 +303,7 @@ func TestBuildContainerGraphFourContainersNoneInCycle(t *testing.T) {
ctr1.config.NetNsCtr = ctr3.config.ID
ctr2.config.UserNsCtr = ctr3.config.ID
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.NoError(t, err)
assert.Equal(t, 4, len(graph.nodes))
assert.Equal(t, 2, len(graph.noDepNodes))
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 313f67963..f51b53e85 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -788,7 +788,7 @@ func (c *Container) startDependencies(ctx context.Context) error {
}
// Build a dependency graph of containers
- graph, err := buildContainerGraph(depCtrs)
+ graph, err := BuildContainerGraph(depCtrs)
if err != nil {
return errors.Wrapf(err, "error generating dependency graph for container %s", c.ID())
}
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index c7b0353bd..e2448e92a 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -37,7 +37,7 @@ func (p *Pod) Start(ctx context.Context) (map[string]error, error) {
}
// Build a dependency graph of containers in the pod
- graph, err := buildContainerGraph(allCtrs)
+ graph, err := BuildContainerGraph(allCtrs)
if err != nil {
return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID())
}
@@ -289,7 +289,7 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) {
}
// Build a dependency graph of containers in the pod
- graph, err := buildContainerGraph(allCtrs)
+ graph, err := BuildContainerGraph(allCtrs)
if err != nil {
return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID())
}
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index 863640f97..41607145d 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -1094,28 +1094,145 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
return portContainers, nil
}
-// GenerateSystemd creates a unit file for a container
-func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
- ctr, err := r.Runtime.LookupContainer(c.InputArgs[0])
+// generateServiceName generates the container name and the service name for systemd service.
+func generateServiceName(c *cliconfig.GenerateSystemdValues, ctr *libpod.Container, pod *libpod.Pod) (string, string) {
+ var kind, name, ctrName string
+ if pod == nil {
+ kind = "container"
+ name = ctr.ID()
+ if c.Name {
+ name = ctr.Name()
+ }
+ ctrName = name
+ } else {
+ kind = "pod"
+ name = pod.ID()
+ ctrName = ctr.ID()
+ if c.Name {
+ name = pod.Name()
+ ctrName = ctr.Name()
+ }
+ }
+ return ctrName, fmt.Sprintf("%s-%s", kind, name)
+}
+
+// generateSystemdgenContainerInfo is a helper to generate a
+// systemdgen.ContainerInfo for `GenerateSystemd`.
+func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSystemdValues, nameOrID string, pod *libpod.Pod) (*systemdgen.ContainerInfo, bool, error) {
+ ctr, err := r.Runtime.LookupContainer(nameOrID)
if err != nil {
- return "", err
+ return nil, false, err
}
+
timeout := int(ctr.StopTimeout())
if c.StopTimeout >= 0 {
timeout = c.StopTimeout
}
- name := ctr.ID()
- if c.Name {
- name = ctr.Name()
- }
config := ctr.Config()
conmonPidFile := config.ConmonPidFile
if conmonPidFile == "" {
- return "", errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ }
+
+ name, serviceName := generateServiceName(c, ctr, pod)
+ info := &systemdgen.ContainerInfo{
+ ServiceName: serviceName,
+ ContainerName: name,
+ RestartPolicy: c.RestartPolicy,
+ PIDFile: conmonPidFile,
+ StopTimeout: timeout,
+ GenerateTimestamp: true,
+ }
+
+ return info, true, nil
+}
+
+// GenerateSystemd creates a unit file for a container or pod.
+func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
+ // First assume it's a container.
+ if info, found, err := r.generateSystemdgenContainerInfo(c, c.InputArgs[0], nil); found && err != nil {
+ return "", err
+ } else if found && err == nil {
+ return systemdgen.CreateContainerSystemdUnit(info, c.Files)
+ }
+
+ // We're either having a pod or garbage.
+ pod, err := r.Runtime.LookupPod(c.InputArgs[0])
+ if err != nil {
+ return "", err
+ }
+
+ // 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())
+ }
+
+ // 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
+ }
+ podInfo, _, err := r.generateSystemdgenContainerInfo(c, infraID, pod)
+ if err != nil {
+ return "", nil
+ }
+
+ // 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 := []*systemdgen.ContainerInfo{podInfo}
+ for ctr, dependencies := range graph.DependencyMap() {
+ // Skip the infra container as we already generated it.
+ if ctr.ID() == infraID {
+ continue
+ }
+ ctrInfo, _, err := r.generateSystemdgenContainerInfo(c, ctr.ID(), nil)
+ 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 := generateServiceName(c, dep, nil)
+ 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 := systemdgen.CreateContainerSystemdUnit(info, c.Files)
+ if err != nil {
+ return "", err
+ }
+ builder.WriteString(out)
}
- return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, conmonPidFile, timeout)
+ return builder.String(), nil
}
// GetNamespaces returns namespace information about a container for PS
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)
}
})
}
diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go
index 5bb040206..314743a92 100644
--- a/test/e2e/generate_systemd_test.go
+++ b/test/e2e/generate_systemd_test.go
@@ -3,10 +3,11 @@
package integration
import (
+ "os"
+
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
- "os"
)
var _ = Describe("Podman generate systemd", func() {
@@ -33,7 +34,7 @@ var _ = Describe("Podman generate systemd", func() {
})
- It("podman generate systemd on bogus container", func() {
+ It("podman generate systemd on bogus container/pod", func() {
session := podmanTest.Podman([]string{"generate", "systemd", "foobar"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
@@ -51,6 +52,19 @@ var _ = Describe("Podman generate systemd", func() {
Expect(session.ExitCode()).To(Not(Equal(0)))
})
+ It("podman generate systemd good timeout value", func() {
+ session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"generate", "systemd", "--timeout", "1234", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ found, _ := session.GrepString(" stop -t 1234 ")
+ Expect(found).To(BeTrue())
+ })
+
It("podman generate systemd", func() {
n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
n.WaitWithDefaultTimeout()
@@ -61,6 +75,23 @@ var _ = Describe("Podman generate systemd", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman generate systemd --files --name", func() {
+ n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--files", "--name", "nginx"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ for _, file := range session.OutputToStringArray() {
+ os.Remove(file)
+ }
+
+ found, _ := session.GrepString("/container-nginx.service")
+ Expect(found).To(BeTrue())
+ })
+
It("podman generate systemd with timeout", func() {
n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx})
n.WaitWithDefaultTimeout()
@@ -69,6 +100,81 @@ var _ = Describe("Podman generate systemd", func() {
session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "5", "nginx"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
+
+ found, _ := session.GrepString("podman stop -t 5")
+ Expect(found).To(BeTrue())
})
+ It("podman generate systemd pod --name", func() {
+ n := podmanTest.Podman([]string{"pod", "create", "--name", "foo"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ n = podmanTest.Podman([]string{"create", "--pod", "foo", "--name", "foo-1", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ n = podmanTest.Podman([]string{"create", "--pod", "foo", "--name", "foo-2", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "42", "--name", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // Grepping the output (in addition to unit tests)
+ found, _ := session.GrepString("# pod-foo.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("Requires=container-foo-1.service container-foo-2.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("# container-foo-1.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString(" start foo-1")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("-infra") // infra container
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("# container-foo-2.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString(" stop -t 42 foo-2")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("BindsTo=pod-foo.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("PIDFile=")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("/userdata/conmon.pid")
+ Expect(found).To(BeTrue())
+ })
+
+ It("podman generate systemd pod --name --files", func() {
+ n := podmanTest.Podman([]string{"pod", "create", "--name", "foo"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ n = podmanTest.Podman([]string{"create", "--pod", "foo", "--name", "foo-1", "alpine", "top"})
+ n.WaitWithDefaultTimeout()
+ Expect(n.ExitCode()).To(Equal(0))
+
+ session := podmanTest.Podman([]string{"generate", "systemd", "--name", "--files", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ for _, file := range session.OutputToStringArray() {
+ os.Remove(file)
+ }
+
+ found, _ := session.GrepString("/pod-foo.service")
+ Expect(found).To(BeTrue())
+
+ found, _ = session.GrepString("/container-foo-1.service")
+ Expect(found).To(BeTrue())
+ })
})