mirror of
https://github.com/containers/podman.git
synced 2025-10-16 18:53:19 +08:00
Merge pull request #5480 from vrothberg/auto-updates
auto update containers in systemd units
This commit is contained in:
56
cmd/podman/autoupdate.go
Normal file
56
cmd/podman/autoupdate.go
Normal file
@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||
"github.com/containers/libpod/pkg/adapter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
autoUpdateCommand cliconfig.AutoUpdateValues
|
||||
autoUpdateDescription = `Auto update containers according to their auto-update policy.
|
||||
|
||||
Auto-update policies are specified with the "io.containers.autoupdate" label.`
|
||||
_autoUpdateCommand = &cobra.Command{
|
||||
Use: "auto-update [flags]",
|
||||
Short: "Auto update containers according to their auto-update policy",
|
||||
Args: noSubArgs,
|
||||
Long: autoUpdateDescription,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
restartCommand.InputArgs = args
|
||||
restartCommand.GlobalFlags = MainGlobalOpts
|
||||
return autoUpdateCmd(&restartCommand)
|
||||
},
|
||||
Example: `podman auto-update`,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
autoUpdateCommand.Command = _autoUpdateCommand
|
||||
autoUpdateCommand.SetHelpTemplate(HelpTemplate())
|
||||
autoUpdateCommand.SetUsageTemplate(UsageTemplate())
|
||||
}
|
||||
|
||||
func autoUpdateCmd(c *cliconfig.RestartValues) error {
|
||||
runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating libpod runtime")
|
||||
}
|
||||
defer runtime.DeferredShutdown(false)
|
||||
|
||||
units, failures := runtime.AutoUpdate()
|
||||
for _, unit := range units {
|
||||
fmt.Println(unit)
|
||||
}
|
||||
var finalErr error
|
||||
if len(failures) > 0 {
|
||||
finalErr = failures[0]
|
||||
for _, e := range failures[1:] {
|
||||
finalErr = errors.Errorf("%v\n%v", finalErr, e)
|
||||
}
|
||||
}
|
||||
return finalErr
|
||||
}
|
@ -54,6 +54,10 @@ type AttachValues struct {
|
||||
SigProxy bool
|
||||
}
|
||||
|
||||
type AutoUpdateValues struct {
|
||||
PodmanCommand
|
||||
}
|
||||
|
||||
type ImagesValues struct {
|
||||
PodmanCommand
|
||||
All bool
|
||||
@ -470,10 +474,11 @@ type RefreshValues struct {
|
||||
|
||||
type RestartValues struct {
|
||||
PodmanCommand
|
||||
All bool
|
||||
Latest bool
|
||||
Running bool
|
||||
Timeout uint
|
||||
All bool
|
||||
AutoUpdate bool
|
||||
Latest bool
|
||||
Running bool
|
||||
Timeout uint
|
||||
}
|
||||
|
||||
type RestoreValues struct {
|
||||
|
@ -11,6 +11,7 @@ const remoteclient = false
|
||||
// Commands that the local client implements
|
||||
func getMainCommands() []*cobra.Command {
|
||||
rootCommands := []*cobra.Command{
|
||||
_autoUpdateCommand,
|
||||
_cpCommand,
|
||||
_playCommand,
|
||||
_loginCommand,
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
ann "github.com/containers/libpod/pkg/annotations"
|
||||
"github.com/containers/libpod/pkg/autoupdate"
|
||||
envLib "github.com/containers/libpod/pkg/env"
|
||||
"github.com/containers/libpod/pkg/errorhandling"
|
||||
"github.com/containers/libpod/pkg/inspect"
|
||||
@ -25,6 +26,7 @@ import (
|
||||
"github.com/containers/libpod/pkg/rootless"
|
||||
"github.com/containers/libpod/pkg/seccomp"
|
||||
cc "github.com/containers/libpod/pkg/spec"
|
||||
systemdGen "github.com/containers/libpod/pkg/systemd/generate"
|
||||
"github.com/containers/libpod/pkg/util"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-units"
|
||||
@ -69,6 +71,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
}
|
||||
|
||||
imageName := ""
|
||||
rawImageName := ""
|
||||
var imageData *inspect.ImageData = nil
|
||||
|
||||
// Set the storage if there is no rootfs specified
|
||||
@ -78,9 +81,8 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
writer = os.Stderr
|
||||
}
|
||||
|
||||
name := ""
|
||||
if len(c.InputArgs) != 0 {
|
||||
name = c.InputArgs[0]
|
||||
rawImageName = c.InputArgs[0]
|
||||
} else {
|
||||
return nil, nil, errors.Errorf("error, image name not provided")
|
||||
}
|
||||
@ -97,7 +99,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
ArchitectureChoice: overrideArch,
|
||||
}
|
||||
|
||||
newImage, err := runtime.ImageRuntime().New(ctx, name, rtc.SignaturePolicyPath, c.String("authfile"), writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullType)
|
||||
newImage, err := runtime.ImageRuntime().New(ctx, rawImageName, rtc.SignaturePolicyPath, c.String("authfile"), writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -174,11 +176,32 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
}
|
||||
}
|
||||
|
||||
createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, imageData)
|
||||
createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, rawImageName, imageData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// (VR): Ideally we perform the checks _before_ pulling the image but that
|
||||
// would require some bigger code refactoring of `ParseCreateOpts` and the
|
||||
// logic here. But as the creation code will be consolidated in the future
|
||||
// and given auto updates are experimental, we can live with that for now.
|
||||
// In the end, the user may only need to correct the policy or the raw image
|
||||
// name.
|
||||
autoUpdatePolicy, autoUpdatePolicySpecified := createConfig.Labels[autoupdate.Label]
|
||||
if autoUpdatePolicySpecified {
|
||||
if _, err := autoupdate.LookupPolicy(autoUpdatePolicy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Now we need to make sure we're having a fully-qualified image reference.
|
||||
if rootfs != "" {
|
||||
return nil, nil, errors.Errorf("auto updates do not work with --rootfs")
|
||||
}
|
||||
// Make sure the input image is a docker.
|
||||
if err := autoupdate.ValidateImageReference(rawImageName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Because parseCreateOpts does derive anything from the image, we add health check
|
||||
// at this point. The rest is done by WithOptions.
|
||||
createConfig.HealthCheck = healthCheck
|
||||
@ -270,7 +293,7 @@ func configurePod(c *GenericCLIResults, runtime *libpod.Runtime, namespaces map[
|
||||
|
||||
// Parses CLI options related to container creation into a config which can be
|
||||
// parsed into an OCI runtime spec
|
||||
func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) {
|
||||
func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime, imageName string, rawImageName string, data *inspect.ImageData) (*cc.CreateConfig, error) {
|
||||
var (
|
||||
inputCommand, command []string
|
||||
memoryLimit, memoryReservation, memorySwap, memoryKernel int64
|
||||
@ -481,12 +504,15 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
"container": "podman",
|
||||
}
|
||||
|
||||
// First transform the os env into a map. We need it for the labels later in
|
||||
// any case.
|
||||
osEnv, err := envLib.ParseSlice(os.Environ())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing host environment variables")
|
||||
}
|
||||
|
||||
// Start with env-host
|
||||
if c.Bool("env-host") {
|
||||
osEnv, err := envLib.ParseSlice(os.Environ())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing host environment variables")
|
||||
}
|
||||
env = envLib.Join(env, osEnv)
|
||||
}
|
||||
|
||||
@ -534,6 +560,10 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
}
|
||||
}
|
||||
|
||||
if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists {
|
||||
labels[systemdGen.EnvVariable] = systemdUnit
|
||||
}
|
||||
|
||||
// ANNOTATIONS
|
||||
annotations := make(map[string]string)
|
||||
|
||||
@ -764,11 +794,12 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
||||
Entrypoint: entrypoint,
|
||||
Env: env,
|
||||
// ExposedPorts: ports,
|
||||
Init: c.Bool("init"),
|
||||
InitPath: c.String("init-path"),
|
||||
Image: imageName,
|
||||
ImageID: imageID,
|
||||
Interactive: c.Bool("interactive"),
|
||||
Init: c.Bool("init"),
|
||||
InitPath: c.String("init-path"),
|
||||
Image: imageName,
|
||||
RawImageName: rawImageName,
|
||||
ImageID: imageID,
|
||||
Interactive: c.Bool("interactive"),
|
||||
// IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6
|
||||
Labels: labels,
|
||||
// LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet
|
||||
|
@ -3334,6 +3334,7 @@ _podman_podman() {
|
||||
"
|
||||
commands="
|
||||
attach
|
||||
auto-update
|
||||
build
|
||||
commit
|
||||
container
|
||||
|
11
contrib/systemd/auto-update/podman-auto-update.service
Normal file
11
contrib/systemd/auto-update/podman-auto-update.service
Normal file
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Podman auto-update service
|
||||
Documentation=man:podman-auto-update(1)
|
||||
Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/podman auto-update
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
9
contrib/systemd/auto-update/podman-auto-update.timer
Normal file
9
contrib/systemd/auto-update/podman-auto-update.timer
Normal file
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Podman auto-update timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
46
docs/source/markdown/podman-auto-update.1.md
Normal file
46
docs/source/markdown/podman-auto-update.1.md
Normal file
@ -0,0 +1,46 @@
|
||||
% podman-auto-update(1)
|
||||
|
||||
## NAME
|
||||
podman-auto-update - Auto update containers according to their auto-update policy
|
||||
|
||||
## SYNOPSIS
|
||||
**podman auto-update**
|
||||
|
||||
## DESCRIPTION
|
||||
`podman auto-update` looks up containers with a specified "io.containers.autoupdate" label (i.e., the auto-update policy).
|
||||
|
||||
If the label is present and set to "image", Podman reaches out to the corresponding registry to check if the image has been updated.
|
||||
An image is considered updated if the digest in the local storage is different than the one of the remote image.
|
||||
If an image must be updated, Podman pulls it down and restarts the systemd unit executing the container.
|
||||
|
||||
At container-creation time, Podman looks up the "PODMAN_SYSTEMD_UNIT" environment variables and stores it verbatim in the container's label.
|
||||
This variable is now set by all systemd units generated by `podman-generate-systemd` and is set to `%n` (i.e., the name of systemd unit starting the container).
|
||||
This data is then being used in the auto-update sequence to instruct systemd (via DBUS) to restart the unit and hence to restart the container.
|
||||
|
||||
Note that `podman auto-update` relies on systemd and requires a fully-qualified image reference (e.g., quay.io/podman/stable:latest) to be used to create the container.
|
||||
This enforcement is necessary to know which image to actually check and pull.
|
||||
If an image ID was used, Podman would not know which image to check/pull anymore.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# Start a container
|
||||
$ podman run -d busybox:latest top
|
||||
bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d
|
||||
|
||||
# Generate a systemd unit for this container
|
||||
$ podman generate systemd --new --files bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d
|
||||
/home/user/containers/libpod/container-bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d.service
|
||||
|
||||
# Load the new systemd unit and start it
|
||||
$ mv ./container-bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d.service ~/.config/systemd/user
|
||||
$ systemctl --user daemon-reload
|
||||
$ systemctl --user start container-bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d.service
|
||||
|
||||
# Auto-update the container
|
||||
$ podman auto-update
|
||||
container-bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d.service
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman(1), podman-generate-systemd(1), podman-run(1), systemd.unit(5)
|
@ -154,6 +154,7 @@ the exit codes follow the `chroot` standard, see below:
|
||||
| Command | Description |
|
||||
| ------------------------------------------------ | --------------------------------------------------------------------------- |
|
||||
| [podman-attach(1)](podman-attach.1.md) | Attach to a running container. |
|
||||
| [podman-auto-update(1)](podman-auto-update.1.md) | Auto update containers according to their auto-update policy |
|
||||
| [podman-build(1)](podman-build.1.md) | Build a container image using a Containerfile. |
|
||||
| [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. |
|
||||
| [podman-container(1)](podman-container.1.md) | Manage containers. |
|
||||
|
@ -49,6 +49,12 @@ for md in $(ls -1 *-*.1.md | grep -v remote);do
|
||||
|
||||
# podman.1.md has a two-column table; podman-*.1.md all have three.
|
||||
parent=$(echo $md | sed -e 's/^\(.*\)-.*$/\1.1.md/')
|
||||
if [[ $parent =~ "podman-auto" ]]; then
|
||||
# podman-auto-update.1.md is special cased as it's structure differs
|
||||
# from that of other man pages where main and sub-commands split by
|
||||
# dashes.
|
||||
parent="podman.1.md"
|
||||
fi
|
||||
x=3
|
||||
if expr -- "$parent" : ".*-" >/dev/null; then
|
||||
x=4
|
||||
@ -90,6 +96,12 @@ for md in *.1.md;do
|
||||
# Get the command name, and confirm that it matches the md file name.
|
||||
cmd=$(echo "$synopsis" | sed -e 's/\(.*\)\*\*.*/\1/' | tr -d \*)
|
||||
md_nodash=$(basename "$md" .1.md | tr '-' ' ')
|
||||
if [[ $md_nodash = 'podman auto update' ]]; then
|
||||
# podman-auto-update.1.md is special cased as it's structure differs
|
||||
# from that of other man pages where main and sub-commands split by
|
||||
# dashes.
|
||||
md_nodash='podman auto-update'
|
||||
fi
|
||||
if [ "$cmd" != "$md_nodash" -a "$cmd" != "podman-remote" ]; then
|
||||
echo
|
||||
printf "Inconsistent program name in SYNOPSIS in %s:\n" $md
|
||||
|
@ -38,6 +38,9 @@ function podman_man() {
|
||||
|
||||
# Special case: there is no podman-help man page, nor need for such.
|
||||
echo "help"
|
||||
# Auto-update differs from other commands as it's a single command, not
|
||||
# a main and sub-command split by a dash.
|
||||
echo "auto-update"
|
||||
elif [ "$@" = "podman-image-trust" ]; then
|
||||
# Special case: set and show aren't actually in a table in the man page
|
||||
echo set
|
||||
|
@ -239,6 +239,12 @@ type ContainerConfig struct {
|
||||
// container has been created with.
|
||||
CreateCommand []string `json:"CreateCommand,omitempty"`
|
||||
|
||||
// RawImageName is the raw and unprocessed name of the image when creating
|
||||
// the container (as specified by the user). May or may not be set. One
|
||||
// use case to store this data are auto-updates where we need the _exact_
|
||||
// name and not some normalized instance of it.
|
||||
RawImageName string `json:"RawImageName,omitempty"`
|
||||
|
||||
// TODO consider breaking these subsections up into smaller structs
|
||||
|
||||
// UID/GID mappings used by the storage
|
||||
@ -503,11 +509,17 @@ func (c *Container) Namespace() string {
|
||||
return c.config.Namespace
|
||||
}
|
||||
|
||||
// Image returns the ID and name of the image used as the container's rootfs
|
||||
// Image returns the ID and name of the image used as the container's rootfs.
|
||||
func (c *Container) Image() (string, string) {
|
||||
return c.config.RootfsImageID, c.config.RootfsImageName
|
||||
}
|
||||
|
||||
// RawImageName returns the unprocessed and not-normalized user-specified image
|
||||
// name.
|
||||
func (c *Container) RawImageName() string {
|
||||
return c.config.RawImageName
|
||||
}
|
||||
|
||||
// ShmDir returns the sources path to be mounted on /dev/shm in container
|
||||
func (c *Container) ShmDir() string {
|
||||
return c.config.ShmDir
|
||||
|
@ -98,6 +98,8 @@ const (
|
||||
|
||||
// Attach ...
|
||||
Attach Status = "attach"
|
||||
// AutoUpdate ...
|
||||
AutoUpdate Status = "auto-update"
|
||||
// Checkpoint ...
|
||||
Checkpoint Status = "checkpoint"
|
||||
// Cleanup ...
|
||||
|
@ -4,50 +4,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/libpod/pkg/rootless"
|
||||
"github.com/coreos/go-systemd/v22/dbus"
|
||||
godbus "github.com/godbus/dbus/v5"
|
||||
"github.com/containers/libpod/pkg/systemd"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func dbusAuthRootlessConnection(createBus func(opts ...godbus.ConnOption) (*godbus.Conn, error)) (*godbus.Conn, error) {
|
||||
conn, err := createBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(rootless.GetRootlessUID()))}
|
||||
|
||||
err = conn.Auth(methods)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func newRootlessConnection() (*dbus.Conn, error) {
|
||||
return dbus.NewConnection(func() (*godbus.Conn, error) {
|
||||
return dbusAuthRootlessConnection(func(opts ...godbus.ConnOption) (*godbus.Conn, error) {
|
||||
path := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "systemd/private")
|
||||
return godbus.Dial(fmt.Sprintf("unix:path=%s", path))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getConnection() (*dbus.Conn, error) {
|
||||
if rootless.IsRootless() {
|
||||
return newRootlessConnection()
|
||||
}
|
||||
return dbus.NewSystemdConnection()
|
||||
}
|
||||
|
||||
// createTimer systemd timers for healthchecks of a container
|
||||
func (c *Container) createTimer() error {
|
||||
if c.disableHealthCheckSystemd() {
|
||||
@ -64,7 +28,7 @@ func (c *Container) createTimer() error {
|
||||
}
|
||||
cmd = append(cmd, "--unit", c.ID(), fmt.Sprintf("--on-unit-inactive=%s", c.HealthCheckConfig().Interval.String()), "--timer-property=AccuracySec=1s", podman, "healthcheck", "run", c.ID())
|
||||
|
||||
conn, err := getConnection()
|
||||
conn, err := systemd.ConnectToDBUS()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to get systemd connection to add healthchecks")
|
||||
}
|
||||
@ -83,7 +47,7 @@ func (c *Container) startTimer() error {
|
||||
if c.disableHealthCheckSystemd() {
|
||||
return nil
|
||||
}
|
||||
conn, err := getConnection()
|
||||
conn, err := systemd.ConnectToDBUS()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to get systemd connection to start healthchecks")
|
||||
}
|
||||
@ -98,7 +62,7 @@ func (c *Container) removeTimer() error {
|
||||
if c.disableHealthCheckSystemd() {
|
||||
return nil
|
||||
}
|
||||
conn, err := getConnection()
|
||||
conn, err := systemd.ConnectToDBUS()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to get systemd connection to remove healthchecks")
|
||||
}
|
||||
|
@ -593,7 +593,7 @@ func WithUser(user string) CtrCreateOption {
|
||||
// other configuration from the image will be added to the config.
|
||||
// TODO: Replace image name and ID with a libpod.Image struct when that is
|
||||
// finished.
|
||||
func WithRootFSFromImage(imageID string, imageName string) CtrCreateOption {
|
||||
func WithRootFSFromImage(imageID, imageName, rawImageName string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return define.ErrCtrFinalized
|
||||
@ -601,7 +601,7 @@ func WithRootFSFromImage(imageID string, imageName string) CtrCreateOption {
|
||||
|
||||
ctr.config.RootfsImageID = imageID
|
||||
ctr.config.RootfsImageName = imageName
|
||||
|
||||
ctr.config.RawImageName = rawImageName
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
|
||||
return nil, errors.Wrapf(err, "error running container create option")
|
||||
}
|
||||
}
|
||||
|
||||
return r.setupContainer(ctx, ctr)
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ const (
|
||||
IDTruncLength = 12
|
||||
)
|
||||
|
||||
func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID string, config *v1.ImageConfig) (*Container, error) {
|
||||
func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawImageName, imgID string, config *v1.ImageConfig) (*Container, error) {
|
||||
|
||||
// Set up generator for infra container defaults
|
||||
g, err := generate.New("linux")
|
||||
@ -127,7 +127,7 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
|
||||
|
||||
containerName := p.ID()[:IDTruncLength] + "-infra"
|
||||
options = append(options, r.WithPod(p))
|
||||
options = append(options, WithRootFSFromImage(imgID, imgName))
|
||||
options = append(options, WithRootFSFromImage(imgID, imgName, rawImageName))
|
||||
options = append(options, WithName(containerName))
|
||||
options = append(options, withIsInfra())
|
||||
|
||||
@ -154,5 +154,5 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container,
|
||||
imageName := newImage.Names()[0]
|
||||
imageID := data.ID
|
||||
|
||||
return r.makeInfraContainer(ctx, p, imageName, imageID, data.Config)
|
||||
return r.makeInfraContainer(ctx, p, imageName, r.config.InfraImage, imageID, data.Config)
|
||||
}
|
||||
|
11
pkg/adapter/autoupdate.go
Normal file
11
pkg/adapter/autoupdate.go
Normal file
@ -0,0 +1,11 @@
|
||||
// +build !remoteclient
|
||||
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"github.com/containers/libpod/pkg/autoupdate"
|
||||
)
|
||||
|
||||
func (r *LocalRuntime) AutoUpdate() ([]string, []error) {
|
||||
return autoupdate.AutoUpdate(r.Runtime)
|
||||
}
|
11
pkg/adapter/autoupdate_remote.go
Normal file
11
pkg/adapter/autoupdate_remote.go
Normal file
@ -0,0 +1,11 @@
|
||||
// +build remoteclient
|
||||
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
)
|
||||
|
||||
func (r *LocalRuntime) AutoUpdate() ([]string, []error) {
|
||||
return nil, []error{define.ErrNotImplemented}
|
||||
}
|
280
pkg/autoupdate/autoupdate.go
Normal file
280
pkg/autoupdate/autoupdate.go
Normal file
@ -0,0 +1,280 @@
|
||||
package autoupdate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/systemd"
|
||||
systemdGen "github.com/containers/libpod/pkg/systemd/generate"
|
||||
"github.com/containers/libpod/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Label denotes the container/pod label key to specify auto-update policies in
|
||||
// container labels.
|
||||
const Label = "io.containers.autoupdate"
|
||||
|
||||
// Policy represents an auto-update policy.
|
||||
type Policy string
|
||||
|
||||
const (
|
||||
// PolicyDefault is the default policy denoting no auto updates.
|
||||
PolicyDefault Policy = "disabled"
|
||||
// PolicyNewImage is the policy to update as soon as there's a new image found.
|
||||
PolicyNewImage = "image"
|
||||
)
|
||||
|
||||
// Map for easy lookups of supported policies.
|
||||
var supportedPolicies = map[string]Policy{
|
||||
"": PolicyDefault,
|
||||
"disabled": PolicyDefault,
|
||||
"image": PolicyNewImage,
|
||||
}
|
||||
|
||||
// LookupPolicy looksup the corresponding Policy for the specified
|
||||
// string. If none is found, an errors is returned including the list of
|
||||
// supported policies.
|
||||
//
|
||||
// Note that an empty string resolved to PolicyDefault.
|
||||
func LookupPolicy(s string) (Policy, error) {
|
||||
policy, exists := supportedPolicies[s]
|
||||
if exists {
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// Sort the keys first as maps are non-deterministic.
|
||||
keys := []string{}
|
||||
for k := range supportedPolicies {
|
||||
if k != "" {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return "", errors.Errorf("invalid auto-update policy %q: valid policies are %+q", s, keys)
|
||||
}
|
||||
|
||||
// ValidateImageReference checks if the specified imageName is a fully-qualified
|
||||
// image reference to the docker transport (without digest). Such a reference
|
||||
// includes a domain, name and tag (e.g., quay.io/podman/stable:latest). The
|
||||
// reference may also be prefixed with "docker://" explicitly indicating that
|
||||
// it's a reference to the docker transport.
|
||||
func ValidateImageReference(imageName string) error {
|
||||
// Make sure the input image is a docker.
|
||||
imageRef, err := alltransports.ParseImageName(imageName)
|
||||
if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
|
||||
return errors.Errorf("auto updates require the docker image transport but image is of transport %q", imageRef.Transport().Name())
|
||||
} else if err != nil {
|
||||
repo, err := reference.Parse(imageName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error enforcing fully-qualified docker transport reference for auto updates")
|
||||
}
|
||||
if _, ok := repo.(reference.NamedTagged); !ok {
|
||||
return errors.Errorf("auto updates require fully-qualified image references (no tag): %q", imageName)
|
||||
}
|
||||
if _, ok := repo.(reference.Digested); ok {
|
||||
return errors.Errorf("auto updates require fully-qualified image references without digest: %q", imageName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoUpdate looks up containers with a specified auto-update policy and acts
|
||||
// accordingly. If the policy is set to PolicyNewImage, it checks if the image
|
||||
// on the remote registry is different than the local one. If the image digests
|
||||
// differ, it pulls the remote image and restarts the systemd unit running the
|
||||
// container.
|
||||
//
|
||||
// It returns a slice of successfully restarted systemd units and a slice of
|
||||
// errors encountered during auto update.
|
||||
func AutoUpdate(runtime *libpod.Runtime) ([]string, []error) {
|
||||
// Create a map from `image ID -> []*Container`.
|
||||
containerMap, errs := imageContainersMap(runtime)
|
||||
if len(containerMap) == 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
// Create a map from `image ID -> *image.Image` for image lookups.
|
||||
imagesSlice, err := runtime.ImageRuntime().GetImages()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
imageMap := make(map[string]*image.Image)
|
||||
for i := range imagesSlice {
|
||||
imageMap[imagesSlice[i].ID()] = imagesSlice[i]
|
||||
}
|
||||
|
||||
// Connect to DBUS.
|
||||
conn, err := systemd.ConnectToDBUS()
|
||||
if err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
return nil, []error{err}
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Update images.
|
||||
containersToRestart := []*libpod.Container{}
|
||||
updatedRawImages := make(map[string]bool)
|
||||
for imageID, containers := range containerMap {
|
||||
image, exists := imageMap[imageID]
|
||||
if !exists {
|
||||
errs = append(errs, errors.Errorf("container image ID %q not found in local storage", imageID))
|
||||
return nil, errs
|
||||
}
|
||||
// Now we have to check if the image of any containers must be updated.
|
||||
// Note that the image ID is NOT enough for this check as a given image
|
||||
// may have multiple tags.
|
||||
for i, ctr := range containers {
|
||||
rawImageName := ctr.RawImageName()
|
||||
if rawImageName == "" {
|
||||
errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", ctr.ID()))
|
||||
}
|
||||
needsUpdate, err := newerImageAvailable(runtime, image, rawImageName)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", ctr.ID(), rawImageName))
|
||||
continue
|
||||
}
|
||||
if !needsUpdate {
|
||||
continue
|
||||
}
|
||||
logrus.Infof("Auto-updating container %q using image %q", ctr.ID(), rawImageName)
|
||||
if _, updated := updatedRawImages[rawImageName]; !updated {
|
||||
_, err = updateImage(runtime, rawImageName)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image update for %q failed", ctr.ID(), rawImageName))
|
||||
continue
|
||||
}
|
||||
updatedRawImages[rawImageName] = true
|
||||
}
|
||||
containersToRestart = append(containersToRestart, containers[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Restart containers.
|
||||
updatedUnits := []string{}
|
||||
for _, ctr := range containersToRestart {
|
||||
labels := ctr.Labels()
|
||||
unit, exists := labels[systemdGen.EnvVariable]
|
||||
if !exists {
|
||||
// Shouldn't happen but let's be sure of it.
|
||||
errs = append(errs, errors.Errorf("error auto-updating container %q: no %s label found", ctr.ID(), systemdGen.EnvVariable))
|
||||
continue
|
||||
}
|
||||
_, err := conn.RestartUnit(unit, "replace", nil)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: restarting systemd unit %q failed", ctr.ID(), unit))
|
||||
continue
|
||||
}
|
||||
logrus.Infof("Successfully restarted systemd unit %q", unit)
|
||||
updatedUnits = append(updatedUnits, unit)
|
||||
}
|
||||
|
||||
return updatedUnits, errs
|
||||
}
|
||||
|
||||
// imageContainersMap generates a map[image ID] -> [containers using the image]
|
||||
// of all containers with a valid auto-update policy.
|
||||
func imageContainersMap(runtime *libpod.Runtime) (map[string][]*libpod.Container, []error) {
|
||||
allContainers, err := runtime.GetAllContainers()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
errors := []error{}
|
||||
imageMap := make(map[string][]*libpod.Container)
|
||||
for i, ctr := range allContainers {
|
||||
state, err := ctr.State()
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
// Only update running containers.
|
||||
if state != define.ContainerStateRunning {
|
||||
continue
|
||||
}
|
||||
// Only update containers with the specific label/policy set.
|
||||
labels := ctr.Labels()
|
||||
if value, exists := labels[Label]; exists {
|
||||
policy, err := LookupPolicy(value)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
if policy != PolicyNewImage {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Now we know that `ctr` is configured for auto updates.
|
||||
id, _ := ctr.Image()
|
||||
imageMap[id] = append(imageMap[id], allContainers[i])
|
||||
}
|
||||
|
||||
return imageMap, errors
|
||||
}
|
||||
|
||||
// newerImageAvailable returns true if there corresponding image on the remote
|
||||
// registry is newer.
|
||||
func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string) (bool, error) {
|
||||
remoteRef, err := docker.ParseReference("//" + origName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
remoteImg, err := remoteRef.NewImage(context.Background(), runtime.SystemContext())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rawManifest, _, err := remoteImg.Manifest(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
remoteDigest, err := manifest.Digest(rawManifest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return img.Digest().String() != remoteDigest.String(), nil
|
||||
}
|
||||
|
||||
// updateImage pulls the specified image.
|
||||
func updateImage(runtime *libpod.Runtime, name string) (*image.Image, error) {
|
||||
sys := runtime.SystemContext()
|
||||
registryOpts := image.DockerRegistryOptions{}
|
||||
signaturePolicyPath := ""
|
||||
authFilePath := ""
|
||||
|
||||
if sys != nil {
|
||||
registryOpts.OSChoice = sys.OSChoice
|
||||
registryOpts.ArchitectureChoice = sys.OSChoice
|
||||
registryOpts.DockerCertPath = sys.DockerCertPath
|
||||
|
||||
signaturePolicyPath = sys.SignaturePolicyPath
|
||||
authFilePath = sys.AuthFilePath
|
||||
}
|
||||
|
||||
newImage, err := runtime.ImageRuntime().New(context.Background(),
|
||||
docker.Transport.Name()+"://"+name,
|
||||
signaturePolicyPath,
|
||||
authFilePath,
|
||||
os.Stderr,
|
||||
®istryOpts,
|
||||
image.SigningOptions{},
|
||||
nil,
|
||||
util.PullImageAlways,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImage, nil
|
||||
}
|
50
pkg/autoupdate/autoupdate_test.go
Normal file
50
pkg/autoupdate/autoupdate_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package autoupdate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateImageReference(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
valid bool
|
||||
}{
|
||||
{ // Fully-qualified reference
|
||||
input: "quay.io/foo/bar:tag",
|
||||
valid: true,
|
||||
},
|
||||
{ // Fully-qualified reference in transport notation
|
||||
input: "docker://quay.io/foo/bar:tag",
|
||||
valid: true,
|
||||
},
|
||||
{ // Fully-qualified reference but with digest
|
||||
input: "quay.io/foo/bar@sha256:c9b1b535fdd91a9855fb7f82348177e5f019329a58c53c47272962dd60f71fc9",
|
||||
valid: false,
|
||||
},
|
||||
{ // Reference with missing tag
|
||||
input: "quay.io/foo/bar",
|
||||
valid: false,
|
||||
},
|
||||
{ // Short name
|
||||
input: "alpine",
|
||||
valid: false,
|
||||
},
|
||||
{ // Short name with repo
|
||||
input: "library/alpine",
|
||||
valid: false,
|
||||
},
|
||||
{ // Wrong transport
|
||||
input: "docker-archive:/some/path.tar",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := ValidateImageReference(test.input)
|
||||
if test.valid && err != nil {
|
||||
t.Fatalf("parsing %q should have succeeded: %v", test.input, err)
|
||||
} else if !test.valid && err == nil {
|
||||
t.Fatalf("parsing %q should have failed", test.input)
|
||||
}
|
||||
}
|
||||
}
|
@ -144,6 +144,7 @@ type CreateConfig struct {
|
||||
InitPath string //init-path
|
||||
Image string
|
||||
ImageID string
|
||||
RawImageName string
|
||||
BuiltinImgVolumes map[string]struct{} // volumes defined in the image config
|
||||
ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore
|
||||
Interactive bool //interactive
|
||||
@ -348,7 +349,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
|
||||
options = append(options, nsOpts...)
|
||||
|
||||
// Gather up the options for NewContainer which consist of With... funcs
|
||||
options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image))
|
||||
options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, c.RawImageName))
|
||||
options = append(options, libpod.WithConmonPidFile(c.ConmonPidFile))
|
||||
options = append(options, libpod.WithLabels(c.Labels))
|
||||
options = append(options, libpod.WithShmSize(c.Resources.ShmSize))
|
||||
|
@ -36,7 +36,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image))
|
||||
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
|
||||
|
||||
runtimeSpec, err := s.toOCISpec(rt, newImage)
|
||||
if err != nil {
|
||||
|
@ -143,6 +143,10 @@ type ContainerStorageConfig struct {
|
||||
// Conflicts with Rootfs.
|
||||
// At least one of Image or Rootfs must be specified.
|
||||
Image string `json:"image"`
|
||||
// RawImageName is the unprocessed and not-normalized user-specified image
|
||||
// name. One use case for having this data at hand are auto-updates where
|
||||
// the _exact_ user input is needed in order to look-up the correct image.
|
||||
RawImageName string `json:"raw_image_name,omitempty"`
|
||||
// Rootfs is the path to a directory that will be used as the
|
||||
// container's root filesystem. No modification will be made to the
|
||||
// directory, it will be directly mounted into the container as root.
|
||||
|
47
pkg/systemd/dbus.go
Normal file
47
pkg/systemd/dbus.go
Normal file
@ -0,0 +1,47 @@
|
||||
package systemd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/containers/libpod/pkg/rootless"
|
||||
"github.com/coreos/go-systemd/v22/dbus"
|
||||
godbus "github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
func dbusAuthRootlessConnection(createBus func(opts ...godbus.ConnOption) (*godbus.Conn, error)) (*godbus.Conn, error) {
|
||||
conn, err := createBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(rootless.GetRootlessUID()))}
|
||||
|
||||
err = conn.Auth(methods)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func newRootlessConnection() (*dbus.Conn, error) {
|
||||
return dbus.NewConnection(func() (*godbus.Conn, error) {
|
||||
return dbusAuthRootlessConnection(func(opts ...godbus.ConnOption) (*godbus.Conn, error) {
|
||||
path := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "systemd/private")
|
||||
return godbus.Dial(fmt.Sprintf("unix:path=%s", path))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ConnectToDBUS returns a DBUS connection. It works both as root and non-root
|
||||
// users.
|
||||
func ConnectToDBUS() (*dbus.Conn, error) {
|
||||
if rootless.IsRootless() {
|
||||
return newRootlessConnection()
|
||||
}
|
||||
return dbus.NewSystemdConnection()
|
||||
}
|
@ -16,6 +16,10 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EnvVariable "PODMAN_SYSTEMD_UNIT" is set in all generated systemd units and
|
||||
// is set to the unit's (unique) name.
|
||||
const EnvVariable = "PODMAN_SYSTEMD_UNIT"
|
||||
|
||||
// ContainerInfo contains data required for generating a container's systemd
|
||||
// unit file.
|
||||
type ContainerInfo struct {
|
||||
@ -57,6 +61,8 @@ type ContainerInfo struct {
|
||||
// RunCommand is a post-processed variant of CreateCommand and used for
|
||||
// the ExecStart field in generic unit files.
|
||||
RunCommand string
|
||||
// EnvVariable is generate.EnvVariable and must not be set.
|
||||
EnvVariable string
|
||||
}
|
||||
|
||||
var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"}
|
||||
@ -94,6 +100,7 @@ Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{
|
||||
{{- end}}
|
||||
|
||||
[Service]
|
||||
Environment={{.EnvVariable}}=%n
|
||||
Restart={{.RestartPolicy}}
|
||||
{{- if .New}}
|
||||
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
|
||||
@ -138,6 +145,8 @@ func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, erro
|
||||
info.Executable = executable
|
||||
}
|
||||
|
||||
info.EnvVariable = EnvVariable
|
||||
|
||||
// Assemble the ExecStart command when creating a new container.
|
||||
//
|
||||
// Note that we cannot catch all corner cases here such that users
|
||||
|
@ -44,6 +44,7 @@ Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
|
||||
ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
|
||||
@ -64,6 +65,7 @@ Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/podman start foobar
|
||||
ExecStop=/usr/bin/podman stop -t 10 foobar
|
||||
@ -88,6 +90,7 @@ BindsTo=a.service b.service c.service pod.service
|
||||
After=a.service b.service c.service pod.service
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/podman start foobar
|
||||
ExecStop=/usr/bin/podman stop -t 10 foobar
|
||||
@ -110,6 +113,7 @@ Requires=container-1.service container-2.service
|
||||
Before=container-1.service container-2.service
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/podman start jadda-jadda-infra
|
||||
ExecStop=/usr/bin/podman stop -t 10 jadda-jadda-infra
|
||||
@ -130,6 +134,7 @@ Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
|
||||
ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
|
||||
@ -152,6 +157,7 @@ Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
|
||||
ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
|
||||
@ -174,6 +180,7 @@ Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
|
||||
ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d awesome-image:latest
|
||||
|
Reference in New Issue
Block a user