Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --tz flag to create, run #6836

Merged
merged 1 commit into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do in podman-remote?

Copy link
Member Author

@ashley-cui ashley-cui Jul 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I haven't done anything in the remote area, should I add the remote implementation to this PR as well? Or save it for another

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you don't do it here, i would exect you to wire up the tz stuff asap for the api (remote) side.

Copy link
Member Author

@ashley-cui ashley-cui Jul 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can definitely do that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baude uhh it looks like somehow i actually got this wired to the api remote side without knowing i did. pleasant surprise(?)

"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() {
baude marked this conversation as resolved.
Show resolved Hide resolved
//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"})
rhatdan marked this conversation as resolved.
Show resolved Hide resolved
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))
ashley-cui marked this conversation as resolved.
Show resolved Hide resolved

})
})