diff options
-rw-r--r-- | cmd/podman/cliconfig/config.go | 8 | ||||
-rw-r--r-- | cmd/podman/generate.go | 1 | ||||
-rw-r--r-- | cmd/podman/generate_systemd.go | 70 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 2 | ||||
-rw-r--r-- | commands.md | 2 | ||||
-rw-r--r-- | completions/bash/podman | 27 | ||||
-rw-r--r-- | docs/podman-generate-systemd.1.md | 69 | ||||
-rw-r--r-- | docs/podman-generate.1.md | 1 | ||||
-rw-r--r-- | docs/podman-run.1.md | 17 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 18 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 5 | ||||
-rw-r--r-- | pkg/systemdgen/systemdgen.go | 43 | ||||
-rw-r--r-- | pkg/varlinkapi/generate.go | 22 | ||||
-rw-r--r-- | test/e2e/generate_systemd_test.go | 74 |
14 files changed, 342 insertions, 17 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 43ba7ddc9..b770aaca0 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -136,12 +136,18 @@ type ExportValues struct { PodmanCommand Output string } - type GenerateKubeValues struct { PodmanCommand Service bool } +type GenerateSystemdValues struct { + PodmanCommand + Name bool + RestartPolicy string + StopTimeout int +} + type HistoryValues struct { PodmanCommand Human bool diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go index a0637ecb2..98bfb00a1 100644 --- a/cmd/podman/generate.go +++ b/cmd/podman/generate.go @@ -18,6 +18,7 @@ var ( // Commands that are universally implemented generateCommands = []*cobra.Command{ _containerKubeCommand, + _containerSystemdCommand, } ) diff --git a/cmd/podman/generate_systemd.go b/cmd/podman/generate_systemd.go new file mode 100644 index 000000000..b4779e512 --- /dev/null +++ b/cmd/podman/generate_systemd.go @@ -0,0 +1,70 @@ +package main + +import ( + "fmt" + + "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" +) + +var ( + containerSystemdCommand cliconfig.GenerateSystemdValues + containerSystemdDescription = `Command generates a systemd unit file for a Podman container + ` + _containerSystemdCommand = &cobra.Command{ + Use: "systemd [flags] CONTAINER | POD", + Short: "Generate a systemd unit file for a Podman container", + Long: containerSystemdDescription, + RunE: func(cmd *cobra.Command, args []string) error { + containerSystemdCommand.InputArgs = args + containerSystemdCommand.GlobalFlags = MainGlobalOpts + containerSystemdCommand.Remote = remoteclient + return generateSystemdCmd(&containerSystemdCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 1 || len(args) < 1 { + return errors.New("provide only one container name or ID") + } + return nil + }, + Example: `podman generate kube ctrID +`, + } +) + +func init() { + containerSystemdCommand.Command = _containerSystemdCommand + containerSystemdCommand.SetHelpTemplate(HelpTemplate()) + containerSystemdCommand.SetUsageTemplate(UsageTemplate()) + flags := containerSystemdCommand.Flags() + flags.BoolVarP(&containerSystemdCommand.Name, "name", "n", false, "use the container name instead of ID") + flags.IntVarP(&containerSystemdCommand.StopTimeout, "timeout", "t", -1, "stop timeout override") + flags.StringVar(&containerSystemdCommand.RestartPolicy, "restart-policy", "on-failure", "applicable systemd restart-policy") +} + +func generateSystemdCmd(c *cliconfig.GenerateSystemdValues) error { + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + // User input stop timeout must be 0 or greater + 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 { + return err + } + fmt.Println(unit) + return nil +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 912d001e9..ace81646c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -1218,6 +1218,8 @@ 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) diff --git a/commands.md b/commands.md index 88290dc1d..e6c211254 100644 --- a/commands.md +++ b/commands.md @@ -25,6 +25,8 @@ Command | Descr [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container | [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive | [podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods | +[podman-generate-kube(1)](/docs/podman-generate-kube.1.md) | Generate Kubernetes YAML based on a container or Pod | +[podman-generate-systemd(1)](/docs/podman-generate-systemd.1.md) | Generate a Systemd unit file for a container | [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image | [podman-image(1)](/docs/podman-image.1.md) | Manage Images | [podman-image-exists(1)](/docs/podman-image-exists.1.md) | Check if an image exists in local storage | diff --git a/completions/bash/podman b/completions/bash/podman index a02a47190..e3c0c1dbf 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -919,6 +919,7 @@ _podman_generate() { " subcommands=" kube + systemd " __podman_subcommands "$subcommands $aliases" && return @@ -2460,6 +2461,32 @@ _podman_generate_kube() { esac } +_podman_generate_systemd() { + local options_with_args=" + --restart-policy + -t + --timeout" + + local boolean_options=" + -h + --help + -n + --name + " + + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + COMPREPLY=( $( compgen -W " + $(__podman_containers --all) + " -- "$cur" ) ) + __ltrim_colon_completions "$cur" + ;; + esac +} + _podman_play_kube() { local options_with_args=" --authfile diff --git a/docs/podman-generate-systemd.1.md b/docs/podman-generate-systemd.1.md new file mode 100644 index 000000000..cc3f098a6 --- /dev/null +++ b/docs/podman-generate-systemd.1.md @@ -0,0 +1,69 @@ +% podman-generate Podman Man Pages +% Brent Baude +% April 2019 +# NAME +podman-generate-systemd- Generate Systemd Unit file + +# SYNOPSIS +**podman generate systemd** [*-n*|*--name*] [*-t*|*--timeout*] [*--restart-policy*] *container* + +# 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. + + +# OPTIONS: + +**--name** **-n** + +Use the name of the container for the start, stop, and description in the unit file + +**--timeout** **-t** + +Override the default stop timeout for the container with the given value. + +**--restart-policy** +Set the SystemD restart policy. The restart-policy must be one of: "no", "on-success", "on-failure", "on-abnormal", +"on-watchdog", "on-abort", or "always". The default policy is *on-failure*. + +## Examples ## + +Create a systemd unit file for a container running nginx: + +``` +$ sudo podman generate systemd nginx +[Unit] +Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container +[Service] +Restart=on-failure +ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc +ExecStop=/usr/bin/podman stop -t 10 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc +KillMode=none +Type=forking +PIDFile=/var/lib/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc.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. +``` +$ sudo podman generate systemd --restart-policy=always -t 1 nginx +[Unit] +Description=c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc Podman Container +[Service] +Restart=always +ExecStart=/usr/bin/podman start c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc +ExecStop=/usr/bin/podman stop -t 1 c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc +KillMode=none +Type=forking +PIDFile=/var/lib/containers/storage/overlay-containers/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc/userdata/c21da63c4783be2ac2cd3487ef8d2ec15ee2a28f63dd8f145e3b05607f31cffc.pid +[Install] +WantedBy=multi-user.target +``` + +## SEE ALSO +podman(1), podman-container(1) + +# HISTORY +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 d1736f38e..5a2386778 100644 --- a/docs/podman-generate.1.md +++ b/docs/podman-generate.1.md @@ -14,6 +14,7 @@ The generate command will create structured output (like YAML) based on a contai | 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. | ## SEE ALSO podman, podman-pod, podman-container diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 5190e5d14..9efb7f51c 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -593,7 +593,7 @@ Not implemented. Restart should be handled via a systemd unit files. Please add your podman commands to a unit file and allow systemd or your init system to handle the -restarting of the container processes. See example below. +restarting of the container processes. See *podman generate systemd*. **--rm**=*true*|*false* @@ -1151,21 +1151,6 @@ the uids and gids from the host. $ podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello ``` -### Running a podman container to restart inside of a systemd unit file - - -``` -[Unit] -Description=My App -[Service] -Restart=always -ExecStart=/usr/bin/podman start -a my_app -ExecStop=/usr/bin/podman stop -t 10 my_app -KillMode=process -[Install] -WantedBy=multi-user.target -``` - ### Configuring Storage Options from the command line Podman allows for the configuration of storage by changing the values diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index eb90ab50e..d575bc9b0 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -18,6 +18,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/containers/libpod/pkg/systemdgen" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -940,3 +941,20 @@ 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 + } + timeout := int(ctr.StopTimeout()) + if c.StopTimeout >= 0 { + timeout = int(c.StopTimeout) + } + name := ctr.ID() + if c.Name { + name = ctr.Name() + } + return systemdgen.CreateSystemdUnitAsString(name, ctr.ID(), c.RestartPolicy, ctr.Config().StaticDir, timeout) +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index b7e353f71..201249fc3 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -956,3 +956,8 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) { } return containers, nil } + +// 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) +} diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go new file mode 100644 index 000000000..3d1c31b5d --- /dev/null +++ b/pkg/systemdgen/systemdgen.go @@ -0,0 +1,43 @@ +package systemdgen + +import ( + "fmt" + "path/filepath" + + "github.com/pkg/errors" +) + +var template = `[Unit] +Description=%s Podman Container +[Service] +Restart=%s +ExecStart=/usr/bin/podman start %s +ExecStop=/usr/bin/podman stop -t %d %s +KillMode=none +Type=forking +PIDFile=%s +[Install] +WantedBy=multi-user.target` + +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) +} + +// CreateSystemdUnitAsString takes variables to create a systemd unit file used to control +// a libpod container +func CreateSystemdUnitAsString(name, cid, restart, pidPath string, stopTimeout int) (string, error) { + if err := ValidateRestartPolicy(restart); err != nil { + return "", err + } + pidFile := filepath.Join(pidPath, fmt.Sprintf("%s.pid", cid)) + unit := fmt.Sprintf(template, name, restart, name, stopTimeout, name, pidFile) + return unit, nil +} diff --git a/pkg/varlinkapi/generate.go b/pkg/varlinkapi/generate.go index bc600c397..9dc20d582 100644 --- a/pkg/varlinkapi/generate.go +++ b/pkg/varlinkapi/generate.go @@ -6,6 +6,7 @@ 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 ... @@ -28,3 +29,24 @@ 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) +} diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go new file mode 100644 index 000000000..940e894bc --- /dev/null +++ b/test/e2e/generate_systemd_test.go @@ -0,0 +1,74 @@ +// +build !remoteclient + +package integration + +import ( + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "os" +) + +var _ = Describe("Podman generate systemd", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman generate systemd on bogus container", func() { + session := podmanTest.Podman([]string{"generate", "systemd", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman generate systemd bad restart policy", func() { + session := podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "never", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman generate systemd bad timeout value", func() { + session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "-1", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman generate systemd", func() { + n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx}) + n.WaitWithDefaultTimeout() + Expect(n.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"generate", "systemd", "nginx"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman generate systemd with timeout", func() { + n := podmanTest.Podman([]string{"run", "--name", "nginx", "-dt", nginx}) + n.WaitWithDefaultTimeout() + Expect(n.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "5", "nginx"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + +}) |