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:
Ashley Cui
2020-06-30 17:21:52 -04:00
parent e84695213e
commit 9a1543caec
17 changed files with 197 additions and 0 deletions

View File

@ -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{},

View File

@ -91,6 +91,7 @@ type ContainerCLIOpts struct {
Systemd string
TmpFS []string
TTY bool
Timezone string
UIDMap []string
Ulimit []string
User string

View File

@ -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
}

View File

@ -2120,6 +2120,7 @@ _podman_container_run() {
--stop-signal
--stop-timeout
--tmpfs
--tz
--subgidname
--subuidname
--sysctl

View File

@ -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

View File

@ -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**

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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.

View File

@ -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":

View File

@ -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

View File

@ -48,3 +48,5 @@ default_sysctls = [
dns_searches=[ "foobar.com", ]
dns_servers=[ "1.2.3.4", ]
dns_options=[ "debug", ]
tz = "Pacific/Honolulu"

View File

@ -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"))
})
})

View File

@ -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"))
})
})

View File

@ -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))
})
})