summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/create.go5
-rw-r--r--cmd/podman/common/create_opts.go1
-rw-r--r--cmd/podman/common/specgen.go1
-rw-r--r--completions/bash/podman1
-rw-r--r--docs/source/markdown/podman-create.1.md12
-rw-r--r--docs/source/markdown/podman-run.1.md12
-rw-r--r--libpod/container.go9
-rw-r--r--libpod/container_inspect.go2
-rw-r--r--libpod/container_internal_linux.go57
-rw-r--r--libpod/define/container_inspect.go3
-rw-r--r--libpod/options.go24
-rw-r--r--pkg/specgen/generate/container_create.go4
-rw-r--r--pkg/specgen/specgen.go3
-rw-r--r--test/apiv2/20-containers.at32
-rw-r--r--test/e2e/config/containers.conf2
-rw-r--r--test/e2e/containers_conf_test.go9
-rw-r--r--test/e2e/create_test.go27
-rw-r--r--test/e2e/run_test.go25
18 files changed, 229 insertions, 0 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index c522bbffb..bbe31d19f 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -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{},
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 8bce48d8d..3183a5cce 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -91,6 +91,7 @@ type ContainerCLIOpts struct {
Systemd string
TmpFS []string
TTY bool
+ Timezone string
UIDMap []string
Ulimit []string
User string
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index c9232654a..225370368 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -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
}
diff --git a/completions/bash/podman b/completions/bash/podman
index c0d9560ed..458090ac4 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -2120,6 +2120,7 @@ _podman_container_run() {
--stop-signal
--stop-timeout
--tmpfs
+ --tz
--subgidname
--subuidname
--sysctl
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 30ac54de2..1fc99cd87 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -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
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 2e2adbc7e..86179e63c 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -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**
diff --git a/libpod/container.go b/libpod/container.go
index 66e444c51..a71692dd8 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -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
+
+}
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 03684ddec..94d5dc93b 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -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
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index c58350d18..8bf6092c3 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -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
+}
diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go
index a3cf4304f..614882467 100644
--- a/libpod/define/container_inspect.go
+++ b/libpod/define/container_inspect.go
@@ -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.
diff --git a/libpod/options.go b/libpod/options.go
index 4038be9b8..c1a8fdbe1 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -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.
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 2b84aa8c5..1ab576869 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -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":
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 361f09379..fe735bc1f 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -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
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 9efebfeb9..25843e61c 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -98,9 +98,41 @@ t GET libpod/images/newrepo:v2/json 200 \
.Comment=bar \
.Config.Cmd[-1]="/bin/foo"
+# Create a container for testing the container initializing later
+podman create -t -i --name myctr $IMAGE ls
+
+# Check configuration before initializing
+t GET libpod/containers/myctr/json 200 \
+ .Id~[0-9a-f]\\{64\\} \
+ .State.Status="configured" \
+ .State.Pid=0 \
+ .ResolvConfPath="" \
+ .HostnamePath="" \
+ .HostsPath="" \
+ .NetworkSettings.SandboxKey=""
+
+cpid_file=$(jq -r '.ConmonPidFile' <<<"$output")
+userdata_path=$(dirname $cpid_file)
+
+# Initializing the container
+t POST libpod/containers/myctr/init '' 204
+
+# Check configuration after initializing
+t GET libpod/containers/myctr/json 200 \
+ .Id~[0-9a-f]\\{64\\} \
+ .State.Status="created" \
+ .State.Pid~[0-9]\\{1\,8\\} \
+ .ResolvConfPath=$userdata_path/resolv.conf \
+ .HostnamePath=$userdata_path/hostname \
+ .HostsPath=$userdata_path/hosts \
+ .NetworkSettings.SandboxKey~.*/netns/cni- \
+ .OCIConfigPath~.*config\.json \
+ .GraphDriver.Data.MergedDir~.*merged
+
t DELETE images/localhost/newrepo:latest?force=true 200
t DELETE images/localhost/newrepo:v1?force=true 200
t DELETE images/localhost/newrepo:v2?force=true 200
t DELETE libpod/containers/$cid 204
+t DELETE libpod/containers/myctr 204
# vim: filetype=sh
diff --git a/test/e2e/config/containers.conf b/test/e2e/config/containers.conf
index a3bdde786..0a07676c4 100644
--- a/test/e2e/config/containers.conf
+++ b/test/e2e/config/containers.conf
@@ -48,3 +48,5 @@ default_sysctls = [
dns_searches=[ "foobar.com", ]
dns_servers=[ "1.2.3.4", ]
dns_options=[ "debug", ]
+
+tz = "Pacific/Honolulu"
diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go
index 26da9486d..d8e5f2e69 100644
--- a/test/e2e/containers_conf_test.go
+++ b/test/e2e/containers_conf_test.go
@@ -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"))
+
+ })
})
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index 5f96f96b1..1e33be013 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -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"))
+ })
+
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index e7b68c60f..f49770727 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -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))
+
+ })
})