mirror of
https://github.com/containers/podman.git
synced 2025-06-07 15:48:37 +08:00
Merge pull request #3800 from vrothberg/generate-pod
generate systemd pod
This commit is contained in:
7
API.md
7
API.md
@ -49,8 +49,6 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
|
||||
|
||||
[func GenerateKube(name: string, service: bool) KubePodService](#GenerateKube)
|
||||
|
||||
[func GenerateSystemd(name: string, restart: string, timeout: int, useName: bool) string](#GenerateSystemd)
|
||||
|
||||
[func GetAttachSockets(name: string) Sockets](#GetAttachSockets)
|
||||
|
||||
[func GetContainer(id: string) Container](#GetContainer)
|
||||
@ -482,11 +480,6 @@ error will be returned. See also [ImportImage](ImportImage).
|
||||
method GenerateKube(name: [string](https://godoc.org/builtin#string), service: [bool](https://godoc.org/builtin#bool)) [KubePodService](#KubePodService)</div>
|
||||
GenerateKube generates a Kubernetes v1 Pod description of a Podman container or pod
|
||||
and its containers. The description is in YAML. See also [ReplayKube](ReplayKube).
|
||||
### <a name="GenerateSystemd"></a>func GenerateSystemd
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
method GenerateSystemd(name: [string](https://godoc.org/builtin#string), restart: [string](https://godoc.org/builtin#string), timeout: [int](https://godoc.org/builtin#int), useName: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
|
||||
|
||||
### <a name="GetAttachSockets"></a>func GetAttachSockets
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
|
@ -156,6 +156,7 @@ type GenerateKubeValues struct {
|
||||
type GenerateSystemdValues struct {
|
||||
PodmanCommand
|
||||
Name bool
|
||||
Files bool
|
||||
RestartPolicy string
|
||||
StopTimeout int
|
||||
}
|
||||
|
@ -18,11 +18,14 @@ var (
|
||||
// Commands that are universally implemented
|
||||
generateCommands = []*cobra.Command{
|
||||
_containerKubeCommand,
|
||||
_containerSystemdCommand,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Systemd-service generation is not supported for remote-clients.
|
||||
if !remoteclient {
|
||||
generateCommands = append(generateCommands, _containerSystemdCommand)
|
||||
}
|
||||
generateCommand.Command = _generateCommand
|
||||
generateCommand.AddCommand(generateCommands...)
|
||||
generateCommand.SetUsageTemplate(UsageTemplate())
|
||||
|
@ -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 {
|
||||
|
@ -1241,8 +1241,6 @@ method GetLayersMapWithImageInfo() -> (layerMap: string)
|
||||
# BuildImageHierarchyMap is for the development of Podman and should not be used.
|
||||
method BuildImageHierarchyMap(name: string) -> (imageInfo: string)
|
||||
|
||||
method GenerateSystemd(name: string, restart: string, timeout: int, useName: bool) -> (unit: string)
|
||||
|
||||
# ImageNotFound means the image could not be found by the provided name or ID in local storage.
|
||||
error ImageNotFound (id: string, reason: string)
|
||||
|
||||
|
@ -4,16 +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
|
||||
@ -27,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)
|
||||
|
@ -12,9 +12,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. |
|
||||
| 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
|
||||
podman, podman-pod, podman-container
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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])
|
||||
if err != nil {
|
||||
return "", err
|
||||
// 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 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")
|
||||
}
|
||||
|
||||
return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, conmonPidFile, timeout)
|
||||
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 builder.String(), nil
|
||||
}
|
||||
|
||||
// GetNamespaces returns namespace information about a container for PS
|
||||
|
@ -951,7 +951,7 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
|
||||
|
||||
// GenerateSystemd creates a systemd until for a container
|
||||
func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
|
||||
return iopodman.GenerateSystemd().Call(r.Conn, c.InputArgs[0], c.RestartPolicy, int64(c.StopTimeout), c.Name)
|
||||
return "", errors.New("systemd generation not supported for remote clients")
|
||||
}
|
||||
|
||||
// GetNamespaces returns namespace information about a container for PS
|
||||
|
@ -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}}
|
||||
|
||||
func createSystemdUnitAsString(exe, name, cid, restart, pidFile string, stopTimeout int) (string, error) {
|
||||
if err := ValidateRestartPolicy(restart); err != nil {
|
||||
[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
|
||||
}
|
||||
|
||||
unit := fmt.Sprintf(template, name, restart, exe, name, exe, stopTimeout, name, pidFile)
|
||||
return unit, nil
|
||||
}
|
||||
|
||||
func getPodmanExecutable() string {
|
||||
podmanExe, err := os.Executable()
|
||||
// Make sure the executable is set.
|
||||
if info.Executable == "" {
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
podmanExe = "/usr/bin/podman"
|
||||
logrus.Warnf("Could not obtain podman executable location, using default %s", podmanExe)
|
||||
executable = "/usr/bin/podman"
|
||||
logrus.Warnf("Could not obtain podman executable location, using default %s", executable)
|
||||
}
|
||||
info.Executable = executable
|
||||
}
|
||||
|
||||
return podmanExe
|
||||
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
|
||||
}
|
||||
|
@ -5,36 +5,41 @@ import (
|
||||
)
|
||||
|
||||
func TestValidateRestartPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
type ContainerInfo struct {
|
||||
restart string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ package varlinkapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/shared"
|
||||
iopodman "github.com/containers/libpod/cmd/podman/varlink"
|
||||
"github.com/containers/libpod/pkg/systemdgen"
|
||||
)
|
||||
|
||||
// GenerateKube ...
|
||||
@ -29,24 +29,3 @@ func (i *LibpodAPI) GenerateKube(call iopodman.VarlinkCall, name string, service
|
||||
Service: string(servB),
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateSystemd ...
|
||||
func (i *LibpodAPI) GenerateSystemd(call iopodman.VarlinkCall, nameOrID, restart string, stopTimeout int64, useName bool) error {
|
||||
ctr, err := i.Runtime.LookupContainer(nameOrID)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
timeout := int(ctr.StopTimeout())
|
||||
if stopTimeout >= 0 {
|
||||
timeout = int(stopTimeout)
|
||||
}
|
||||
name := ctr.ID()
|
||||
if useName {
|
||||
name = ctr.Name()
|
||||
}
|
||||
unit, err := systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), restart, ctr.Config().StaticDir, timeout)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
return call.ReplyGenerateSystemd(unit)
|
||||
}
|
||||
|
@ -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())
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user