Skip to content

Commit

Permalink
Add --tz flag to create, run
Browse files Browse the repository at this point in the history
--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 <[email protected]>
  • Loading branch information
ashley-cui committed Jul 2, 2020
1 parent e846952 commit 9a1543c
Show file tree
Hide file tree
Showing 17 changed files with 197 additions and 0 deletions.
5 changes: 5 additions & 0 deletions cmd/podman/common/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down
1 change: 1 addition & 0 deletions cmd/podman/common/create_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type ContainerCLIOpts struct {
Systemd string
TmpFS []string
TTY bool
Timezone string
UIDMap []string
Ulimit []string
User string
Expand Down
1 change: 1 addition & 0 deletions cmd/podman/common/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions completions/bash/podman
Original file line number Diff line number Diff line change
Expand Up @@ -2120,6 +2120,7 @@ _podman_container_run() {
--stop-signal
--stop-timeout
--tmpfs
--tz
--subgidname
--subuidname
--sysctl
Expand Down
12 changes: 12 additions & 0 deletions docs/source/markdown/podman-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/source/markdown/podman-run.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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**
Expand Down
9 changes: 9 additions & 0 deletions libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

}
2 changes: 2 additions & 0 deletions libpod/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
57 changes: 57 additions & 0 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
3 changes: 3 additions & 0 deletions libpod/define/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions pkg/specgen/generate/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
3 changes: 3 additions & 0 deletions pkg/specgen/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/config/containers.conf
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ default_sysctls = [
dns_searches=[ "foobar.com", ]
dns_servers=[ "1.2.3.4", ]
dns_options=[ "debug", ]

tz = "Pacific/Honolulu"
9 changes: 9 additions & 0 deletions test/e2e/containers_conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

})
})
27 changes: 27 additions & 0 deletions test/e2e/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
})

})
25 changes: 25 additions & 0 deletions test/e2e/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

})
})

0 comments on commit 9a1543c

Please sign in to comment.