mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00
Add --tz flag to create, run
--tz flag sets timezone inside container Can be set to IANA timezone as well as `local` to match host machine Signed-off-by: Ashley Cui <acui@redhat.com>
This commit is contained in:
@ -449,6 +449,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
|
||||
"tty", "t", false,
|
||||
"Allocate a pseudo-TTY for container",
|
||||
)
|
||||
createFlags.StringVar(
|
||||
&cf.Timezone,
|
||||
"tz", containerConfig.TZ(),
|
||||
"Set timezone in container",
|
||||
)
|
||||
createFlags.StringSliceVar(
|
||||
&cf.UIDMap,
|
||||
"uidmap", []string{},
|
||||
|
@ -91,6 +91,7 @@ type ContainerCLIOpts struct {
|
||||
Systemd string
|
||||
TmpFS []string
|
||||
TTY bool
|
||||
Timezone string
|
||||
UIDMap []string
|
||||
Ulimit []string
|
||||
User string
|
||||
|
@ -619,6 +619,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
|
||||
}
|
||||
s.Remove = c.Rm
|
||||
s.StopTimeout = &c.StopTimeout
|
||||
s.Timezone = c.Timezone
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -2120,6 +2120,7 @@ _podman_container_run() {
|
||||
--stop-signal
|
||||
--stop-timeout
|
||||
--tmpfs
|
||||
--tz
|
||||
--subgidname
|
||||
--subuidname
|
||||
--sysctl
|
||||
|
@ -815,6 +815,10 @@ interactive shell. The default is false.
|
||||
Note: The **-t** option is incompatible with a redirection of the Podman client
|
||||
standard input.
|
||||
|
||||
**--tz**=*timezone*
|
||||
|
||||
Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones.
|
||||
|
||||
**--uidmap**=*container_uid:host_uid:amount*
|
||||
|
||||
UID map for the user namespace. Using this flag will run the container with user namespace enabled. It conflicts with the `--userns` and `--subuidname` flags.
|
||||
@ -1036,6 +1040,14 @@ the uids and gids from the host.
|
||||
$ podman create --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello
|
||||
```
|
||||
|
||||
### Configure timezone in a container
|
||||
|
||||
```
|
||||
$ podman create --tz=local alpine date
|
||||
$ podman create --tz=Asia/Shanghai alpine date
|
||||
$ podman create --tz=US/Eastern alpine date
|
||||
```
|
||||
|
||||
### Rootless Containers
|
||||
|
||||
Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils
|
||||
|
@ -856,6 +856,10 @@ interactive shell. The default is **false**.
|
||||
**NOTE**: The **-t** option is incompatible with a redirection of the Podman client
|
||||
standard input.
|
||||
|
||||
**--tz**=*timezone*
|
||||
|
||||
Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones.
|
||||
|
||||
**--uidmap**=*container_uid*:*host_uid*:*amount*
|
||||
|
||||
Run the container in a new user namespace using the supplied mapping. This option conflicts
|
||||
@ -1319,6 +1323,14 @@ using global options.
|
||||
podman --log-level=debug --storage-driver overlay --storage-opt "overlay.mount_program=/usr/bin/fuse-overlayfs" run busybox /bin/sh
|
||||
```
|
||||
|
||||
### Configure timezone in a container
|
||||
|
||||
```
|
||||
$ podman run --tz=local alpine date
|
||||
$ podman run --tz=Asia/Shanghai alpine date
|
||||
$ podman run --tz=US/Eastern alpine date
|
||||
```
|
||||
|
||||
### Rootless Containers
|
||||
|
||||
Podman runs as a non root user on most systems. This feature requires that a new enough version of **shadow-utils**
|
||||
|
@ -424,6 +424,10 @@ type ContainerConfig struct {
|
||||
// to 0, 1, 2) that will be passed to the executed process. The total FDs
|
||||
// passed will be 3 + PreserveFDs.
|
||||
PreserveFDs uint `json:"preserveFds,omitempty"`
|
||||
|
||||
// Timezone is the timezone inside the container.
|
||||
// Local means it has the same timezone as the host machine
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerNamedVolume is a named volume that will be mounted into the
|
||||
@ -1248,3 +1252,8 @@ func (c *Container) AutoRemove() bool {
|
||||
}
|
||||
return c.Spec().Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue
|
||||
}
|
||||
|
||||
func (c *Container) Timezone() string {
|
||||
return c.config.Timezone
|
||||
|
||||
}
|
||||
|
@ -322,6 +322,8 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp
|
||||
|
||||
ctrConfig.CreateCommand = c.config.CreateCommand
|
||||
|
||||
ctrConfig.Timezone = c.config.Timezone
|
||||
|
||||
return ctrConfig
|
||||
}
|
||||
|
||||
|
@ -1241,6 +1241,31 @@ func (c *Container) makeBindMounts() error {
|
||||
c.state.BindMounts["/etc/hostname"] = hostnamePath
|
||||
}
|
||||
|
||||
// Make /etc/localtime
|
||||
if c.Timezone() != "" {
|
||||
if _, ok := c.state.BindMounts["/etc/localtime"]; !ok {
|
||||
var zonePath string
|
||||
if c.Timezone() == "local" {
|
||||
zonePath, err = filepath.EvalSymlinks("/etc/localtime")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding local timezone for container %s", c.ID())
|
||||
}
|
||||
} else {
|
||||
zone := filepath.Join("/usr/share/zoneinfo", c.Timezone())
|
||||
zonePath, err = filepath.EvalSymlinks(zone)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error setting timezone for container %s", c.ID())
|
||||
}
|
||||
}
|
||||
localtimePath, err := c.copyTimezoneFile(zonePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error setting timezone for container %s", c.ID())
|
||||
}
|
||||
c.state.BindMounts["/etc/localtime"] = localtimePath
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Make .containerenv
|
||||
// Empty file, so no need to recreate if it exists
|
||||
if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
|
||||
@ -1533,3 +1558,35 @@ func (c *Container) getOCICgroupPath() (string, error) {
|
||||
return "", errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager %s requested", c.runtime.config.Engine.CgroupManager)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) copyTimezoneFile(zonePath string) (string, error) {
|
||||
var localtimeCopy string = filepath.Join(c.state.RunDir, "localtime")
|
||||
file, err := os.Stat(zonePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if file.IsDir() {
|
||||
return "", errors.New("Invalid timezone: is a directory")
|
||||
}
|
||||
src, err := os.Open(zonePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer src.Close()
|
||||
dest, err := os.Create(localtimeCopy)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer dest.Close()
|
||||
_, err = io.Copy(dest, src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := label.Relabel(localtimeCopy, c.config.MountLabel, false); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := dest.Chown(c.RootUID(), c.RootGID()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return localtimeCopy, err
|
||||
}
|
||||
|
@ -54,6 +54,9 @@ type InspectContainerConfig struct {
|
||||
// CreateCommand is the full command plus arguments of the process the
|
||||
// container has been created with.
|
||||
CreateCommand []string `json:"CreateCommand,omitempty"`
|
||||
// Timezone is the timezone inside the container.
|
||||
// Local means it has the same timezone as the host machine
|
||||
Timezone string `json:"Timezone,omitempty"`
|
||||
}
|
||||
|
||||
// InspectRestartPolicy holds information about the container's restart policy.
|
||||
|
@ -1525,6 +1525,30 @@ func withSetAnon() VolumeCreateOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimezone sets the timezone in the container
|
||||
func WithTimezone(path string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return define.ErrCtrFinalized
|
||||
}
|
||||
if path != "local" {
|
||||
zone := filepath.Join("/usr/share/zoneinfo", path)
|
||||
|
||||
file, err := os.Stat(zone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//We don't want to mount a timezone directory
|
||||
if file.IsDir() {
|
||||
return errors.New("Invalid timezone: is a directory")
|
||||
}
|
||||
}
|
||||
|
||||
ctr.config.Timezone = path
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pod Creation Options
|
||||
|
||||
// WithPodName sets the name of the pod.
|
||||
|
@ -135,6 +135,10 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
|
||||
options = append(options, libpod.WithStdin())
|
||||
}
|
||||
|
||||
if s.Timezone != "" {
|
||||
options = append(options, libpod.WithTimezone(s.Timezone))
|
||||
}
|
||||
|
||||
useSystemd := false
|
||||
switch s.Systemd {
|
||||
case "always":
|
||||
|
@ -135,6 +135,9 @@ type ContainerBasicConfig struct {
|
||||
// passed will be 3 + PreserveFDs.
|
||||
// set tags as `json:"-"` for not supported remote
|
||||
PreserveFDs uint `json:"-"`
|
||||
// Timezone is the timezone inside the container.
|
||||
// Local means it has the same timezone as the host machine
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerStorageConfig contains information on the storage configuration of a
|
||||
|
@ -48,3 +48,5 @@ default_sysctls = [
|
||||
dns_searches=[ "foobar.com", ]
|
||||
dns_servers=[ "1.2.3.4", ]
|
||||
dns_options=[ "debug", ]
|
||||
|
||||
tz = "Pacific/Honolulu"
|
||||
|
@ -211,4 +211,13 @@ var _ = Describe("Podman run", func() {
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.LineInOuputStartsWith("search")).To(BeFalse())
|
||||
})
|
||||
|
||||
It("podman run containers.conf timezone", func() {
|
||||
//containers.conf timezone set to Pacific/Honolulu
|
||||
session := podmanTest.Podman([]string{"run", ALPINE, "date", "+'%H %Z'"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring("HST"))
|
||||
|
||||
})
|
||||
})
|
||||
|
@ -471,4 +471,31 @@ var _ = Describe("Podman create", func() {
|
||||
Expect(len(data)).To(Equal(1))
|
||||
Expect(data[0].Config.StopSignal).To(Equal(uint(15)))
|
||||
})
|
||||
|
||||
It("podman create --tz", func() {
|
||||
session := podmanTest.Podman([]string{"create", "--tz", "foo", "--name", "bad", ALPINE, "date"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "--tz", "America", "--name", "dir", ALPINE, "date"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "--tz", "Pacific/Honolulu", "--name", "zone", ALPINE, "date"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
inspect := podmanTest.Podman([]string{"inspect", "zone"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
data := inspect.InspectContainerToJSON()
|
||||
Expect(len(data)).To(Equal(1))
|
||||
Expect(data[0].Config.Timezone).To(Equal("Pacific/Honolulu"))
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "--tz", "local", "--name", "lcl", ALPINE, "date"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
inspect = podmanTest.Podman([]string{"inspect", "lcl"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
data = inspect.InspectContainerToJSON()
|
||||
Expect(len(data)).To(Equal(1))
|
||||
Expect(data[0].Config.Timezone).To(Equal("local"))
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -1047,4 +1047,29 @@ USER mail`
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(strings.Contains(session.OutputToString(), groupName)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("podman run --tz", func() {
|
||||
session := podmanTest.Podman([]string{"run", "--tz", "foo", "--rm", ALPINE, "date"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--tz", "America", "--rm", ALPINE, "date"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--tz", "Pacific/Honolulu", "--rm", ALPINE, "date", "+'%H %Z'"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring("HST"))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--tz", "local", "--rm", ALPINE, "date", "+'%H %Z'"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
t := time.Now()
|
||||
z, _ := t.Zone()
|
||||
h := strconv.Itoa(t.Hour())
|
||||
Expect(session.OutputToString()).To(ContainSubstring(z))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(h))
|
||||
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user