diff options
-rw-r--r-- | cmd/podman/common/create.go | 52 | ||||
-rw-r--r-- | docs/source/markdown/podman-container-clone.1.md | 27 | ||||
-rw-r--r-- | docs/source/markdown/podman-create.1.md | 6 | ||||
-rw-r--r-- | docs/source/markdown/podman-run.1.md | 6 | ||||
-rw-r--r-- | libpod/container_config.go | 2 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 19 | ||||
-rw-r--r-- | libpod/events.go | 5 | ||||
-rw-r--r-- | libpod/events/config.go | 4 | ||||
-rw-r--r-- | libpod/events/events.go | 21 | ||||
-rw-r--r-- | libpod/events/logfile.go | 156 | ||||
-rw-r--r-- | libpod/events/logfile_test.go | 140 | ||||
-rw-r--r-- | libpod/options.go | 13 | ||||
-rw-r--r-- | pkg/domain/entities/pods.go | 2 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 37 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 2 | ||||
-rw-r--r-- | pkg/specgenutil/specgen.go | 5 | ||||
-rw-r--r-- | test/e2e/container_clone_test.go | 14 | ||||
-rw-r--r-- | test/e2e/run_passwd_test.go | 12 | ||||
-rw-r--r-- | test/system/090-events.bats | 65 |
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" +} |