summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/create.go52
-rw-r--r--docs/source/markdown/podman-container-clone.1.md27
-rw-r--r--docs/source/markdown/podman-create.1.md6
-rw-r--r--docs/source/markdown/podman-run.1.md6
-rw-r--r--libpod/container_config.go2
-rw-r--r--libpod/container_internal_linux.go19
-rw-r--r--libpod/events.go5
-rw-r--r--libpod/events/config.go4
-rw-r--r--libpod/events/events.go21
-rw-r--r--libpod/events/logfile.go156
-rw-r--r--libpod/events/logfile_test.go140
-rw-r--r--libpod/options.go13
-rw-r--r--pkg/domain/entities/pods.go2
-rw-r--r--pkg/specgen/generate/container_create.go37
-rw-r--r--pkg/specgen/specgen.go2
-rw-r--r--pkg/specgenutil/specgen.go5
-rw-r--r--test/e2e/container_clone_test.go14
-rw-r--r--test/e2e/run_passwd_test.go12
-rw-r--r--test/system/090-events.bats65
19 files changed, 520 insertions, 68 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 8a3f02036..62c9f4c9a 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -312,30 +312,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt)
- memoryReservationFlagName := "memory-reservation"
- createFlags.StringVar(
- &cf.MemoryReservation,
- memoryReservationFlagName, "",
- "Memory soft limit "+sizeWithUnitFormat,
- )
- _ = cmd.RegisterFlagCompletionFunc(memoryReservationFlagName, completion.AutocompleteNone)
-
- memorySwapFlagName := "memory-swap"
- createFlags.StringVar(
- &cf.MemorySwap,
- memorySwapFlagName, "",
- "Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
- )
- _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone)
-
- memorySwappinessFlagName := "memory-swappiness"
- createFlags.Int64Var(
- &cf.MemorySwappiness,
- memorySwappinessFlagName, -1,
- "Tune container memory swappiness (0 to 100, or -1 for system default)",
- )
- _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone)
-
createFlags.BoolVar(
&cf.NoHealthCheck,
"no-healthcheck", false,
@@ -630,6 +606,10 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(chrootDirsFlagName, completion.AutocompleteDefault)
+ passwdEntryName := "passwd-entry"
+ createFlags.StringVar(&cf.PasswdEntry, passwdEntryName, "", "Entry to write to /etc/passwd")
+ _ = cmd.RegisterFlagCompletionFunc(passwdEntryName, completion.AutocompleteNone)
+
if registry.IsRemote() {
_ = createFlags.MarkHidden("env-host")
_ = createFlags.MarkHidden("http-proxy")
@@ -891,6 +871,30 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
"Memory limit "+sizeWithUnitFormat,
)
_ = cmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone)
+
+ memoryReservationFlagName := "memory-reservation"
+ createFlags.StringVar(
+ &cf.MemoryReservation,
+ memoryReservationFlagName, "",
+ "Memory soft limit "+sizeWithUnitFormat,
+ )
+ _ = cmd.RegisterFlagCompletionFunc(memoryReservationFlagName, completion.AutocompleteNone)
+
+ memorySwapFlagName := "memory-swap"
+ createFlags.StringVar(
+ &cf.MemorySwap,
+ memorySwapFlagName, "",
+ "Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone)
+
+ memorySwappinessFlagName := "memory-swappiness"
+ createFlags.Int64Var(
+ &cf.MemorySwappiness,
+ memorySwappinessFlagName, -1,
+ "Tune container memory swappiness (0 to 100, or -1 for system default)",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone)
}
//anyone can use these
cpusFlagName := "cpus"
diff --git a/docs/source/markdown/podman-container-clone.1.md b/docs/source/markdown/podman-container-clone.1.md
index 6c23abe81..7d5e1c262 100644
--- a/docs/source/markdown/podman-container-clone.1.md
+++ b/docs/source/markdown/podman-container-clone.1.md
@@ -137,6 +137,33 @@ system's page size (the value would be very large, that's millions of trillions)
If no memory limits are specified, the original container's will be used.
+#### **--memory-reservation**=*limit*
+
+Memory soft limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
+
+After setting memory reservation, when the system detects memory contention
+or low memory, containers are forced to restrict their consumption to their
+reservation. So you should always set the value below **--memory**, otherwise the
+hard limit will take precedence. By default, memory reservation will be the same
+as memory limit from the container being cloned.
+
+#### **--memory-swap**=*limit*
+
+A limit value equal to memory plus swap. Must be used with the **-m**
+(**--memory**) flag. The swap `LIMIT` should always be larger than **-m**
+(**--memory**) value. By default, the swap `LIMIT` will be set to double
+the value of --memory if specified. Otherwise, the container being cloned will be used to derive the swap value.
+
+The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes),
+`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a
+unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
+
+#### **--memory-swappiness**=*number*
+
+Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
+
+This flag is not supported on cgroups V2 systems.
+
#### **--name**
Set a custom name for the cloned container. The default if not specified is of the syntax: **<ORIGINAL_NAME>-clone**
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index c4d27e321..9af7174fc 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -755,6 +755,12 @@ Tune the host's OOM preferences for containers (accepts -1000 to 1000)
#### **--os**=*OS*
Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`.
+#### **--passwd-entry**=*ENTRY*
+
+Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used.
+
+The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime.
+
#### **--personality**=*persona*
Personality sets the execution domain via Linux personality(2).
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index e4ccd0368..ecb24ba6d 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -787,6 +787,12 @@ Override the OS, defaults to hosts, of the image to be pulled. For example, `win
Allow Podman to add entries to /etc/passwd and /etc/group when used in conjunction with the --user option.
This is used to override the Podman provided user setup in favor of entrypoint configurations such as libnss-extrausers.
+#### **--passwd-entry**=*ENTRY*
+
+Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used.
+
+The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime.
+
#### **--personality**=*persona*
Personality sets the execution domain via Linux personality(2).
diff --git a/libpod/container_config.go b/libpod/container_config.go
index ea644764c..8500c6db9 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -404,6 +404,8 @@ type ContainerMiscConfig struct {
// InitContainerType specifies if the container is an initcontainer
// and if so, what type: always or once are possible non-nil entries
InitContainerType string `json:"init_container_type,omitempty"`
+ // PasswdEntry specifies arbitrary data to append to a file.
+ PasswdEntry string `json:"passwd_entry,omitempty"`
}
// InfraInherit contains the compatible options inheritable from the infra container
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 11ca169ca..9369b746c 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -2724,6 +2724,9 @@ func (c *Container) userPasswdEntry(u *user.User) (string, error) {
if !hasHomeSet {
c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", homeDir))
}
+ if c.config.PasswdEntry != "" {
+ return c.passwdEntry(u.Username, u.Uid, u.Gid, u.Name, homeDir), nil
+ }
return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), nil
}
@@ -2775,9 +2778,25 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err
gid = group.Gid
}
}
+
+ if c.config.PasswdEntry != "" {
+ entry := c.passwdEntry(fmt.Sprintf("%d", uid), fmt.Sprintf("%d", uid), fmt.Sprintf("%d", gid), "container user", c.WorkingDir())
+ return entry, int(uid), gid, nil
+ }
+
return fmt.Sprintf("%d:*:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), int(uid), gid, nil
}
+func (c *Container) passwdEntry(username string, uid, gid, name, homeDir string) string {
+ s := c.config.PasswdEntry
+ s = strings.Replace(s, "$USERNAME", username, -1)
+ s = strings.Replace(s, "$UID", uid, -1)
+ s = strings.Replace(s, "$GID", gid, -1)
+ s = strings.Replace(s, "$NAME", name, -1)
+ s = strings.Replace(s, "$HOME", homeDir, -1)
+ return s + "\n"
+}
+
// generatePasswdAndGroup generates container-specific passwd and group files
// iff g.config.User is a number or we are configured to make a passwd entry for
// the current user or the user specified HostsUsers
diff --git a/libpod/events.go b/libpod/events.go
index d6595180a..3908536a1 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -13,8 +13,9 @@ import (
// newEventer returns an eventer that can be used to read/write events
func (r *Runtime) newEventer() (events.Eventer, error) {
options := events.EventerOptions{
- EventerType: r.config.Engine.EventsLogger,
- LogFilePath: r.config.Engine.EventsLogFilePath,
+ EventerType: r.config.Engine.EventsLogger,
+ LogFilePath: r.config.Engine.EventsLogFilePath,
+ LogFileMaxSize: r.config.Engine.EventsLogFileMaxSize,
}
return events.NewEventer(options)
}
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 188d15578..35680a275 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -60,6 +60,8 @@ type EventerOptions struct {
// LogFilePath is the path to where the log file should reside if using
// the file logger
LogFilePath string
+ // LogFileMaxSize is the default limit used for rotating the log file
+ LogFileMaxSize uint64
}
// Eventer is the interface for journald or file event logging
@@ -171,6 +173,8 @@ const (
Restart Status = "restart"
// Restore ...
Restore Status = "restore"
+ // Rotate indicates that the log file was rotated
+ Rotate Status = "log-rotation"
// Save ...
Save Status = "save"
// Start ...
diff --git a/libpod/events/events.go b/libpod/events/events.go
index 2cdd2ab67..1745095fb 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -3,11 +3,9 @@ package events
import (
"encoding/json"
"fmt"
- "os"
"time"
"github.com/containers/storage/pkg/stringid"
- "github.com/nxadm/tail"
"github.com/pkg/errors"
)
@@ -87,7 +85,11 @@ func (e *Event) ToHumanReadable(truncate bool) string {
case Image:
humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, id, e.Name)
case System:
- humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status)
+ if e.Name != "" {
+ humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name)
+ } else {
+ humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status)
+ }
case Volume:
humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name)
}
@@ -196,6 +198,8 @@ func StringToStatus(name string) (Status, error) {
return Restart, nil
case Restore.String():
return Restore, nil
+ case Rotate.String():
+ return Rotate, nil
case Save.String():
return Save, nil
case Start.String():
@@ -215,14 +219,3 @@ func StringToStatus(name string) (Status, error) {
}
return "", errors.Errorf("unknown event status %q", name)
}
-
-func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) {
- reopen := true
- seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
- if options.FromStart || !options.Stream {
- seek.Whence = 0
- reopen = false
- }
- stream := options.Stream
- return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true})
-}
diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go
index 76173cde9..5091f3723 100644
--- a/libpod/events/logfile.go
+++ b/libpod/events/logfile.go
@@ -1,15 +1,24 @@
+//go:build linux
+// +build linux
+
package events
import (
+ "bufio"
"context"
"fmt"
+ "io"
+ "io/ioutil"
"os"
+ "path"
"time"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/pkg/lockfile"
+ "github.com/nxadm/tail"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
// EventLogFile is the structure for event writing to a logfile. It contains the eventer
@@ -27,21 +36,55 @@ func (e EventLogFile) Write(ee Event) error {
}
lock.Lock()
defer lock.Unlock()
- f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
+
+ eventJSONString, err := ee.ToJSONString()
if err != nil {
return err
}
- defer f.Close()
- eventJSONString, err := ee.ToJSONString()
+
+ rotated, err := rotateLog(e.options.LogFilePath, eventJSONString, e.options.LogFileMaxSize)
+ if err != nil {
+ return fmt.Errorf("rotating log file: %w", err)
+ }
+
+ if rotated {
+ rEvent := NewEvent(Rotate)
+ rEvent.Type = System
+ rEvent.Name = e.options.LogFilePath
+ rotateJSONString, err := rEvent.ToJSONString()
+ if err != nil {
+ return err
+ }
+ if err := e.writeString(rotateJSONString); err != nil {
+ return err
+ }
+ }
+
+ return e.writeString(eventJSONString)
+}
+
+func (e EventLogFile) writeString(s string) error {
+ f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
if err != nil {
return err
}
- if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil {
+ if _, err := f.WriteString(s + "\n"); err != nil {
return err
}
return nil
}
+func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) {
+ reopen := true
+ seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
+ if options.FromStart || !options.Stream {
+ seek.Whence = 0
+ reopen = false
+ }
+ stream := options.Stream
+ return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true})
+}
+
// Reads from the log file
func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
defer close(options.EventChannel)
@@ -107,3 +150,108 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
func (e EventLogFile) String() string {
return LogFile.String()
}
+
+// Rotates the log file if the log file size and content exceeds limit
+func rotateLog(logfile string, content string, limit uint64) (bool, error) {
+ if limit == 0 {
+ return false, nil
+ }
+ file, err := os.Stat(logfile)
+ if err != nil {
+ return false, err
+ }
+ var filesize = uint64(file.Size())
+ var contentsize = uint64(len([]rune(content)))
+ if filesize+contentsize < limit {
+ return false, nil
+ }
+
+ if err := truncate(logfile); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// Truncates the log file and saves 50% of content to new log file
+func truncate(filePath string) error {
+ orig, err := os.Open(filePath)
+ if err != nil {
+ return err
+ }
+ defer orig.Close()
+
+ origFinfo, err := orig.Stat()
+ if err != nil {
+ return err
+ }
+
+ size := origFinfo.Size()
+ threshold := size / 2
+
+ tmp, err := ioutil.TempFile(path.Dir(filePath), "")
+ if err != nil {
+ // Retry in /tmp in case creating a tmp file in the same
+ // directory has failed.
+ tmp, err = ioutil.TempFile("", "")
+ if err != nil {
+ return err
+ }
+ }
+ defer tmp.Close()
+
+ // Jump directly to the threshold, drop the first line and copy the remainder
+ if _, err := orig.Seek(threshold, 0); err != nil {
+ return err
+ }
+ reader := bufio.NewReader(orig)
+ if _, err := reader.ReadString('\n'); err != nil {
+ if !errors.Is(err, io.EOF) {
+ return err
+ }
+ }
+ if _, err := reader.WriteTo(tmp); err != nil {
+ return fmt.Errorf("writing truncated contents: %w", err)
+ }
+
+ if err := renameLog(tmp.Name(), filePath); err != nil {
+ return fmt.Errorf("writing back %s to %s: %w", tmp.Name(), filePath, err)
+ }
+
+ return nil
+}
+
+// Renames from, to
+func renameLog(from, to string) error {
+ err := os.Rename(from, to)
+ if err == nil {
+ return nil
+ }
+
+ if !errors.Is(err, unix.EXDEV) {
+ return err
+ }
+
+ // Files are not on the same partition, so we need to copy them the
+ // hard way.
+ fFrom, err := os.Open(from)
+ if err != nil {
+ return err
+ }
+ defer fFrom.Close()
+
+ fTo, err := os.Create(to)
+ if err != nil {
+ return err
+ }
+ defer fTo.Close()
+
+ if _, err := io.Copy(fTo, fFrom); err != nil {
+ return fmt.Errorf("writing back from temporary file: %w", err)
+ }
+
+ if err := os.Remove(from); err != nil {
+ return fmt.Errorf("removing temporary file: %w", err)
+ }
+
+ return nil
+}
diff --git a/libpod/events/logfile_test.go b/libpod/events/logfile_test.go
new file mode 100644
index 000000000..302533c12
--- /dev/null
+++ b/libpod/events/logfile_test.go
@@ -0,0 +1,140 @@
+package events
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestRotateLog(t *testing.T) {
+ tests := []struct {
+ // If sizeInitial + sizeContent >= sizeLimit, then rotate
+ sizeInitial uint64
+ sizeContent uint64
+ sizeLimit uint64
+ mustRotate bool
+ }{
+ // No rotation
+ {0, 0, 1, false},
+ {1, 1, 0, false},
+ {10, 10, 30, false},
+ {1000, 500, 1600, false},
+ // Rotation
+ {10, 10, 20, true},
+ {30, 0, 29, true},
+ {200, 50, 150, true},
+ {1000, 600, 1500, true},
+ }
+
+ for _, test := range tests {
+ tmp, err := ioutil.TempFile("", "log-rotation-")
+ require.NoError(t, err)
+ defer os.Remove(tmp.Name())
+ defer tmp.Close()
+
+ // Create dummy file and content.
+ initialContent := make([]byte, test.sizeInitial)
+ logContent := make([]byte, test.sizeContent)
+
+ // Write content to the file.
+ _, err = tmp.Write(initialContent)
+ require.NoError(t, err)
+
+ // Now rotate
+ fInfoBeforeRotate, err := tmp.Stat()
+ require.NoError(t, err)
+ isRotated, err := rotateLog(tmp.Name(), string(logContent), test.sizeLimit)
+ require.NoError(t, err)
+
+ fInfoAfterRotate, err := os.Stat(tmp.Name())
+ // Test if rotation was successful
+ if test.mustRotate {
+ // File has been renamed
+ require.True(t, isRotated)
+ require.NoError(t, err, "log file has been renamed")
+ require.NotEqual(t, fInfoBeforeRotate.Size(), fInfoAfterRotate.Size())
+ } else {
+ // File has not been renamed
+ require.False(t, isRotated)
+ require.NoError(t, err, "log file has not been renamed")
+ require.Equal(t, fInfoBeforeRotate.Size(), fInfoAfterRotate.Size())
+ }
+ }
+}
+
+func TestTruncationOutput(t *testing.T) {
+ contentBefore := `0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+`
+ contentAfter := `6
+7
+8
+9
+10
+`
+ // Create dummy file
+ tmp, err := ioutil.TempFile("", "log-rotation")
+ require.NoError(t, err)
+ defer os.Remove(tmp.Name())
+ defer tmp.Close()
+
+ // Write content before truncation to dummy file
+ _, err = tmp.WriteString(contentBefore)
+ require.NoError(t, err)
+
+ // Truncate the file
+ beforeTruncation, err := ioutil.ReadFile(tmp.Name())
+ require.NoError(t, err)
+ err = truncate(tmp.Name())
+ require.NoError(t, err)
+ afterTruncation, err := ioutil.ReadFile(tmp.Name())
+ require.NoError(t, err)
+
+ // Test if rotation was successful
+ require.NoError(t, err, "Log content has changed")
+ require.NotEqual(t, beforeTruncation, afterTruncation)
+ require.Equal(t, string(afterTruncation), contentAfter)
+}
+
+func TestRenameLog(t *testing.T) {
+ fileContent := `0
+1
+2
+3
+4
+5
+`
+ // Create two dummy files
+ source, err := ioutil.TempFile("", "removing")
+ require.NoError(t, err)
+ target, err := ioutil.TempFile("", "renaming")
+ require.NoError(t, err)
+
+ // Write to source dummy file
+ _, err = source.WriteString(fileContent)
+ require.NoError(t, err)
+
+ // Rename the files
+ beforeRename, err := ioutil.ReadFile(source.Name())
+ require.NoError(t, err)
+ err = renameLog(source.Name(), target.Name())
+ require.NoError(t, err)
+ afterRename, err := ioutil.ReadFile(target.Name())
+ require.NoError(t, err)
+
+ // Test if renaming was successful
+ require.Error(t, os.Remove(source.Name()))
+ require.NoError(t, os.Remove(target.Name()))
+ require.Equal(t, beforeRename, afterRename)
+}
diff --git a/libpod/options.go b/libpod/options.go
index 2e5454393..6c4b4cc42 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -2051,3 +2051,16 @@ func WithChrootDirs(dirs []string) CtrCreateOption {
return nil
}
}
+
+// WithPasswdEntry sets the entry to write to the /etc/passwd file.
+func WithPasswdEntry(passwdEntry string) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+
+ ctr.config.PasswdEntry = passwdEntry
+
+ return nil
+ }
+}
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index f1d445c4b..1e25e0872 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -272,6 +272,8 @@ type ContainerCreateOptions struct {
Net *NetOptions `json:"net,omitempty"`
CgroupConf []string
+
+ PasswdEntry string
}
func NewInfraContainerCreateOptions() ContainerCreateOptions {
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 6a611e854..5667a02e8 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -8,7 +8,6 @@ import (
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
"github.com/containers/common/libimage"
- "github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/namespaces"
@@ -184,32 +183,19 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if err != nil {
return nil, nil, nil, err
}
-
- switch {
- case s.ResourceLimits.CPU != nil:
- runtimeSpec.Linux.Resources.CPU = s.ResourceLimits.CPU
- case s.ResourceLimits.Memory != nil:
- runtimeSpec.Linux.Resources.Memory = s.ResourceLimits.Memory
- case s.ResourceLimits.BlockIO != nil:
- runtimeSpec.Linux.Resources.BlockIO = s.ResourceLimits.BlockIO
- case s.ResourceLimits.Devices != nil:
- runtimeSpec.Linux.Resources.Devices = s.ResourceLimits.Devices
- }
-
- cgroup2, err := cgroups.IsCgroup2UnifiedMode()
- if err != nil {
- return nil, nil, nil, err
- }
- if cgroup2 && s.ResourceLimits.Memory != nil && s.ResourceLimits.Memory.Swappiness != nil { // conf.Spec.Linux contains memory swappiness established after the spec process we need to remove that
- s.ResourceLimits.Memory.Swappiness = nil
- if runtimeSpec.Linux.Resources.Memory != nil {
- runtimeSpec.Linux.Resources.Memory.Swappiness = nil
+ if s.ResourceLimits != nil {
+ switch {
+ case s.ResourceLimits.CPU != nil:
+ runtimeSpec.Linux.Resources.CPU = s.ResourceLimits.CPU
+ case s.ResourceLimits.Memory != nil:
+ runtimeSpec.Linux.Resources.Memory = s.ResourceLimits.Memory
+ case s.ResourceLimits.BlockIO != nil:
+ runtimeSpec.Linux.Resources.BlockIO = s.ResourceLimits.BlockIO
+ case s.ResourceLimits.Devices != nil:
+ runtimeSpec.Linux.Resources.Devices = s.ResourceLimits.Devices
}
}
}
- if err != nil {
- return nil, nil, nil, err
- }
if len(s.HostDeviceList) > 0 {
options = append(options, libpod.WithHostDevice(s.HostDeviceList))
}
@@ -286,6 +272,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if s.Volatile {
options = append(options, libpod.WithVolatile())
}
+ if s.PasswdEntry != "" {
+ options = append(options, libpod.WithPasswdEntry(s.PasswdEntry))
+ }
useSystemd := false
switch s.Systemd {
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index dfac1d457..79e20667b 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -206,6 +206,8 @@ type ContainerBasicConfig struct {
UnsetEnvAll bool `json:"unsetenvall,omitempty"`
// Passwd is a container run option that determines if we are validating users/groups before running the container
Passwd *bool `json:"manage_password,omitempty"`
+ // PasswdEntry specifies arbitrary data to append to a file.
+ PasswdEntry string `json:"passwd_entry,omitempty"`
}
// ContainerStorageConfig contains information on the storage configuration of a
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
index 7d4fca846..42b66d909 100644
--- a/pkg/specgenutil/specgen.go
+++ b/pkg/specgenutil/specgen.go
@@ -832,6 +832,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
if s.Passwd == nil {
s.Passwd = &t
}
+
+ if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 {
+ s.PasswdEntry = c.PasswdEntry
+ }
+
return nil
}
diff --git a/test/e2e/container_clone_test.go b/test/e2e/container_clone_test.go
index a327bb8ed..1d5944d1a 100644
--- a/test/e2e/container_clone_test.go
+++ b/test/e2e/container_clone_test.go
@@ -146,6 +146,20 @@ var _ = Describe("Podman container clone", func() {
cloneData = cloneInspect.InspectContainerToJSON()
Expect(createData[0].HostConfig.NanoCpus).ToNot(Equal(cloneData[0].HostConfig.NanoCpus))
Expect(cloneData[0].HostConfig.NanoCpus).To(Equal(nanoCPUs))
+
+ create = podmanTest.Podman([]string{"create", ALPINE})
+ create.WaitWithDefaultTimeout()
+ Expect(create).To(Exit(0))
+ clone = podmanTest.Podman([]string{"container", "clone", "--cpus=4", create.OutputToString()})
+ clone.WaitWithDefaultTimeout()
+ Expect(clone).To(Exit(0))
+
+ cloneInspect = podmanTest.Podman([]string{"inspect", clone.OutputToString()})
+ cloneInspect.WaitWithDefaultTimeout()
+ Expect(cloneInspect).To(Exit(0))
+ cloneData = cloneInspect.InspectContainerToJSON()
+ Expect(cloneData[0].HostConfig.MemorySwappiness).To(Equal(int64(0)))
+
})
It("podman container clone in a pod", func() {
diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go
index 4c97e665a..ce6c6ffda 100644
--- a/test/e2e/run_passwd_test.go
+++ b/test/e2e/run_passwd_test.go
@@ -137,4 +137,16 @@ USER 1000`, ALPINE)
Expect(run).Should(Exit(0))
Expect(run.OutputToString()).NotTo((ContainSubstring("1234:1234")))
})
+
+ It("podman run --passwd-entry flag", func() {
+ // Test that the line we add doesn't contain anything else than what is specified
+ run := podmanTest.Podman([]string{"run", "--user", "1234:1234", "--passwd-entry=FOO", ALPINE, "grep", "^FOO$", "/etc/passwd"})
+ run.WaitWithDefaultTimeout()
+ Expect(run).Should(Exit(0))
+
+ run = podmanTest.Podman([]string{"run", "--user", "12345:12346", "-w", "/etc", "--passwd-entry=$UID-$GID-$NAME-$HOME-$USERNAME", ALPINE, "cat", "/etc/passwd"})
+ run.WaitWithDefaultTimeout()
+ Expect(run).Should(Exit(0))
+ Expect(run.OutputToString()).To(ContainSubstring("12345-12346-container user-/etc-12345"))
+ })
})
diff --git a/test/system/090-events.bats b/test/system/090-events.bats
index a0b0380a2..cac0a177c 100644
--- a/test/system/090-events.bats
+++ b/test/system/090-events.bats
@@ -129,3 +129,68 @@ EOF
run cat $events_file
is "$output" ".*\"Name\":\"$IMAGE" "test"
}
+
+function _populate_events_file() {
+ # Create 100 duplicate entries to populate the events log file.
+ local events_file=$1
+ truncate --size=0 $events_file
+ for i in {0..99}; do
+ printf '{"Name":"busybox","Status":"pull","Time":"2022-04-06T11:26:42.7236679%02d+02:00","Type":"image","Attributes":null}\n' $i >> $events_file
+ done
+}
+
+@test "events log-file rotation" {
+ skip_if_remote "setting CONTAINERS_CONF logger options does not affect remote client"
+
+ # Make sure that the events log file is (not) rotated depending on the
+ # settings in containers.conf.
+
+ # Config without a limit
+ eventsFile=$PODMAN_TMPDIR/events.txt
+ _populate_events_file $eventsFile
+ containersConf=$PODMAN_TMPDIR/containers.conf
+ cat >$containersConf <<EOF
+[engine]
+events_logger="file"
+events_logfile_path="$eventsFile"
+EOF
+
+ # Create events *without* a limit and make sure that it has not been
+ # rotated/truncated.
+ contentBefore=$(head -n100 $eventsFile)
+ CONTAINERS_CONF=$containersConf run_podman run --rm $IMAGE true
+ contentAfter=$(head -n100 $eventsFile)
+ is "$contentBefore" "$contentAfter" "events file has not been rotated"
+
+ # Repopulate events file
+ rm $eventsFile
+ _populate_events_file $eventsFile
+
+ # Config with a limit
+ rm $containersConf
+ cat >$containersConf <<EOF
+[engine]
+events_logger="file"
+events_logfile_path="$eventsFile"
+# The limit of 4750 is the *exact* half of the inital events file.
+events_logfile_max_size=4750
+EOF
+
+ # Create events *with* a limit and make sure that it has been
+ # rotated/truncated. Once rotated, the events file should only contain the
+ # second half of its previous events plus the new ones.
+ expectedContentAfterTruncation=$PODMAN_TMPDIR/truncated.txt
+
+ run_podman create $IMAGE
+ CONTAINERS_CONF=$containersConf run_podman rm $output
+ tail -n52 $eventsFile >> $expectedContentAfterTruncation
+
+ # Make sure the events file looks as expected.
+ is "$(cat $eventsFile)" "$(cat $expectedContentAfterTruncation)" "events file has been rotated"
+
+ # Make sure that `podman events` can read the file, and that it returns the
+ # same amount of events. We checked the contents before.
+ CONTAINERS_CONF=$containersConf run_podman events --stream=false --since="2022-03-06T11:26:42.723667984+02:00"
+ is "$(wc -l <$eventsFile)" "$(wc -l <<<$output)" "all events are returned"
+ is "${lines[-2]}" ".* log-rotation $eventsFile"
+}