mirror of
https://github.com/containers/podman.git
synced 2025-05-17 23:26:08 +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,
|
"tty", "t", false,
|
||||||
"Allocate a pseudo-TTY for container",
|
"Allocate a pseudo-TTY for container",
|
||||||
)
|
)
|
||||||
|
createFlags.StringVar(
|
||||||
|
&cf.Timezone,
|
||||||
|
"tz", containerConfig.TZ(),
|
||||||
|
"Set timezone in container",
|
||||||
|
)
|
||||||
createFlags.StringSliceVar(
|
createFlags.StringSliceVar(
|
||||||
&cf.UIDMap,
|
&cf.UIDMap,
|
||||||
"uidmap", []string{},
|
"uidmap", []string{},
|
||||||
|
@ -91,6 +91,7 @@ type ContainerCLIOpts struct {
|
|||||||
Systemd string
|
Systemd string
|
||||||
TmpFS []string
|
TmpFS []string
|
||||||
TTY bool
|
TTY bool
|
||||||
|
Timezone string
|
||||||
UIDMap []string
|
UIDMap []string
|
||||||
Ulimit []string
|
Ulimit []string
|
||||||
User string
|
User string
|
||||||
|
@ -619,6 +619,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
|
|||||||
}
|
}
|
||||||
s.Remove = c.Rm
|
s.Remove = c.Rm
|
||||||
s.StopTimeout = &c.StopTimeout
|
s.StopTimeout = &c.StopTimeout
|
||||||
|
s.Timezone = c.Timezone
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2120,6 +2120,7 @@ _podman_container_run() {
|
|||||||
--stop-signal
|
--stop-signal
|
||||||
--stop-timeout
|
--stop-timeout
|
||||||
--tmpfs
|
--tmpfs
|
||||||
|
--tz
|
||||||
--subgidname
|
--subgidname
|
||||||
--subuidname
|
--subuidname
|
||||||
--sysctl
|
--sysctl
|
||||||
|
@ -815,6 +815,10 @@ interactive shell. The default is false.
|
|||||||
Note: The **-t** option is incompatible with a redirection of the Podman client
|
Note: The **-t** option is incompatible with a redirection of the Podman client
|
||||||
standard input.
|
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*
|
**--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.
|
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
|
$ 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
|
### Rootless Containers
|
||||||
|
|
||||||
Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils
|
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
|
**NOTE**: The **-t** option is incompatible with a redirection of the Podman client
|
||||||
standard input.
|
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*
|
**--uidmap**=*container_uid*:*host_uid*:*amount*
|
||||||
|
|
||||||
Run the container in a new user namespace using the supplied mapping. This option conflicts
|
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
|
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
|
### Rootless Containers
|
||||||
|
|
||||||
Podman runs as a non root user on most systems. This feature requires that a new enough version of **shadow-utils**
|
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
|
// to 0, 1, 2) that will be passed to the executed process. The total FDs
|
||||||
// passed will be 3 + PreserveFDs.
|
// passed will be 3 + PreserveFDs.
|
||||||
PreserveFDs uint `json:"preserveFds,omitempty"`
|
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
|
// 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
|
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.CreateCommand = c.config.CreateCommand
|
||||||
|
|
||||||
|
ctrConfig.Timezone = c.config.Timezone
|
||||||
|
|
||||||
return ctrConfig
|
return ctrConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1241,6 +1241,31 @@ func (c *Container) makeBindMounts() error {
|
|||||||
c.state.BindMounts["/etc/hostname"] = hostnamePath
|
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
|
// Make .containerenv
|
||||||
// Empty file, so no need to recreate if it exists
|
// Empty file, so no need to recreate if it exists
|
||||||
if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
|
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)
|
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
|
// CreateCommand is the full command plus arguments of the process the
|
||||||
// container has been created with.
|
// container has been created with.
|
||||||
CreateCommand []string `json:"CreateCommand,omitempty"`
|
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.
|
// 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
|
// Pod Creation Options
|
||||||
|
|
||||||
// WithPodName sets the name of the pod.
|
// 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())
|
options = append(options, libpod.WithStdin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.Timezone != "" {
|
||||||
|
options = append(options, libpod.WithTimezone(s.Timezone))
|
||||||
|
}
|
||||||
|
|
||||||
useSystemd := false
|
useSystemd := false
|
||||||
switch s.Systemd {
|
switch s.Systemd {
|
||||||
case "always":
|
case "always":
|
||||||
|
@ -135,6 +135,9 @@ type ContainerBasicConfig struct {
|
|||||||
// passed will be 3 + PreserveFDs.
|
// passed will be 3 + PreserveFDs.
|
||||||
// set tags as `json:"-"` for not supported remote
|
// set tags as `json:"-"` for not supported remote
|
||||||
PreserveFDs uint `json:"-"`
|
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
|
// ContainerStorageConfig contains information on the storage configuration of a
|
||||||
|
@ -48,3 +48,5 @@ default_sysctls = [
|
|||||||
dns_searches=[ "foobar.com", ]
|
dns_searches=[ "foobar.com", ]
|
||||||
dns_servers=[ "1.2.3.4", ]
|
dns_servers=[ "1.2.3.4", ]
|
||||||
dns_options=[ "debug", ]
|
dns_options=[ "debug", ]
|
||||||
|
|
||||||
|
tz = "Pacific/Honolulu"
|
||||||
|
@ -211,4 +211,13 @@ var _ = Describe("Podman run", func() {
|
|||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
Expect(session.LineInOuputStartsWith("search")).To(BeFalse())
|
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(len(data)).To(Equal(1))
|
||||||
Expect(data[0].Config.StopSignal).To(Equal(uint(15)))
|
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(session.ExitCode()).To(Equal(0))
|
||||||
Expect(strings.Contains(session.OutputToString(), groupName)).To(BeTrue())
|
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