diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 5e43f51361..ca886b137d 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -716,6 +716,13 @@ func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) return getNetworks(cmd, toComplete, completeDefault) } +// AutocompleteHostsFile - Autocomplete hosts file options. +// -> "image", "none", paths +func AutocompleteHostsFile(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + hostsFileModes := []string{"image", "none"} + return hostsFileModes, cobra.ShellCompDirectiveDefault +} + // AutocompleteDefaultOneArg - Autocomplete path only for the first argument. func AutocompleteDefaultOneArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index 97257efebc..37ac2c46d8 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -26,6 +26,13 @@ func DefineNetFlags(cmd *cobra.Command) { ) _ = cmd.RegisterFlagCompletionFunc(addHostFlagName, completion.AutocompleteNone) + hostsFileFlagName := "hosts-file" + netFlags.String( + hostsFileFlagName, "", + `Path to a hosts file to copy the entries into the container, or one of the special values. ("image"|"none")`, + ) + _ = cmd.RegisterFlagCompletionFunc(hostsFileFlagName, AutocompleteHostsFile) + dnsFlagName := "dns" netFlags.StringSlice( dnsFlagName, podmanConfig.ContainersConf.DNSServers(), diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 15e714b342..ea28b34046 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -313,6 +313,9 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra if c.Flag("shm-size-systemd").Changed { vals.ShmSizeSystemd = c.Flag("shm-size-systemd").Value.String() } + if c.Flag("hosts-file").Changed { + vals.HostsFile = c.Flag("hosts-file").Value.String() + } if (c.Flag("dns").Changed || c.Flag("dns-option").Changed || c.Flag("dns-search").Changed) && vals.Net != nil && (vals.Net.Network.NSMode == specgen.NoNetwork || vals.Net.Network.IsContainer()) { return vals, errors.New("conflicting options: dns and the network mode: " + string(vals.Net.Network.NSMode)) } diff --git a/docs/source/markdown/options/hosts-file.md b/docs/source/markdown/options/hosts-file.md new file mode 100644 index 0000000000..c5b65aaebc --- /dev/null +++ b/docs/source/markdown/options/hosts-file.md @@ -0,0 +1,12 @@ +####> This option file is used in: +####> podman create, pod create, run +####> If file is edited, make sure the changes +####> are applicable to all of those. +#### **--hosts-file**=*path* | *none* | *image* + +Base file to create the `/etc/hosts` file inside the container. This must either +be an absolute path to a file on the host system, or one of the following +special flags: + "" Follow the `base_hosts_file` configuration in _containers.conf_ (the default) + `none` Do not use a base file (i.e. start with an empty file) + `image` Use the container image's `/etc/hosts` file as base file diff --git a/docs/source/markdown/podman-create.1.md.in b/docs/source/markdown/podman-create.1.md.in index ed5852f8f8..2225a3a582 100644 --- a/docs/source/markdown/podman-create.1.md.in +++ b/docs/source/markdown/podman-create.1.md.in @@ -199,6 +199,8 @@ Print usage statement @@option hostname.container +@@option hosts-file + @@option hostuser @@option http-proxy diff --git a/docs/source/markdown/podman-pod-create.1.md.in b/docs/source/markdown/podman-pod-create.1.md.in index 1441f1ed06..10a05fea97 100644 --- a/docs/source/markdown/podman-pod-create.1.md.in +++ b/docs/source/markdown/podman-pod-create.1.md.in @@ -88,6 +88,8 @@ Print usage statement. @@option hostname.pod +@@option hosts-file + #### **--infra** Create an infra container and associate it with the pod. An infra container is a lightweight container used to coordinate the shared kernel namespace of a pod. Default: true. diff --git a/docs/source/markdown/podman-run.1.md.in b/docs/source/markdown/podman-run.1.md.in index 418b49624c..a5c967ab4e 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -233,6 +233,8 @@ Print usage statement @@option hostname.container +@@option hosts-file + @@option hostuser @@option http-proxy diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 96c4a3bf66..46dd223746 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -178,6 +178,7 @@ type ContainerCreateOptions struct { HealthTimeout string HealthOnFailure string Hostname string `json:"hostname,omitempty"` + HostsFile string HTTPProxy bool HostUsers []string ImageVolume string diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 0bc9b419a7..0ce2df9639 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -642,6 +642,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if len(s.Hostname) == 0 || len(c.Hostname) != 0 { s.Hostname = c.Hostname } + + if len(s.BaseHostsFile) == 0 || len(c.HostsFile) != 0 { + s.BaseHostsFile = c.HostsFile + } + sysctl := map[string]string{} if ctl := c.Sysctl; len(ctl) > 0 { sysctl, err = util.ValidateSysctls(ctl) diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 94e61468c3..5f29562811 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -463,6 +463,60 @@ var _ = Describe("Verify podman containers.conf usage", func() { Expect(session.OutputToString()).To(ContainSubstring("test")) }) + Describe("base_hosts_file in containers.conf", func() { + var baseHostsFile string + var session *PodmanSessionIntegration + + JustBeforeEach(func() { + confFile := filepath.Join(podmanTest.TempDir, "containers.conf") + err = os.WriteFile(confFile, []byte(fmt.Sprintf("[containers]\nbase_hosts_file=\"%s\"\nno_hosts=false\n", baseHostsFile)), 0755) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF_OVERRIDE", confFile) + if IsRemote() { + podmanTest.RestartRemoteService() + } + + session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + }) + + Describe("base_hosts_file=/path/to/hosts", func() { + BeforeEach(func() { + hostsFile := filepath.Join(podmanTest.TempDir, "hosts") + err := os.WriteFile(hostsFile, []byte("12.34.56.78 test.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + baseHostsFile = hostsFile + }) + + It("should use the hosts file from the file path", func() { + Expect(session.OutputToString()).To(ContainSubstring("12.34.56.78 test.example.com")) + }) + }) + + Describe("base_hosts_file=image", func() { + BeforeEach(func() { + baseHostsFile = "image" + }) + + It("should use the hosts file from the container image", func() { + Expect(session.OutputToString()).To(ContainSubstring("localhost localhost.localdomain")) + }) + }) + + Describe("base_hosts_file=none", func() { + BeforeEach(func() { + baseHostsFile = "none" + }) + + It("should not use any hosts files", func() { + Expect(session.OutputToString()).ToNot(ContainSubstring("localhost.localdomain")) + Expect(session.OutputToString()).To(ContainSubstring("localhost")) + }) + }) + }) + It("seccomp profile path", func() { configPath := filepath.Join(podmanTest.TempDir, "containers.conf") os.Setenv("CONTAINERS_CONF", configPath) diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 12fc929310..a454181475 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -149,6 +149,34 @@ var _ = Describe("Podman pod create", func() { Expect(podCreate).Should(ExitWithError(125, "NoInfra and HostAdd are mutually exclusive pod options: invalid pod spec")) }) + It("podman create pod with --hosts-file", func() { + // This test only work locally + if IsRemote() { + GinkgoWriter.Println("Bypassing subsequent tests due to remote environment") + return + } + + confFile := filepath.Join(podmanTest.TempDir, "containers.conf") + err := os.WriteFile(confFile, []byte("[containers]\nbase_hosts_file=\"none\"\n"), 0755) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF", confFile) + + hostsFile := filepath.Join(podmanTest.TempDir, "hosts") + err = os.WriteFile(hostsFile, []byte("12.34.56.78 test.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + // Create flag should override containers.conf + name := "test" + podCreate := podmanTest.Podman([]string{"pod", "create", "--hosts-file=" + hostsFile, "--name", name}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitCleanly()) + + session := podmanTest.Podman([]string{"run", "--pod", name, "--rm", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(ContainSubstring("12.34.56.78 test.example.com")) + }) + It("podman create pod with DNS server set", func() { name := "test" server := "12.34.56.78" diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index b956a8a9cd..f433f9226d 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1519,6 +1519,22 @@ VOLUME %s`, ALPINE, volPath, volPath) Expect(session).To(ExitWithError(125, "--no-hosts and --add-host cannot be set together")) }) + It("podman run with --hosts-file", func() { + confFile := filepath.Join(podmanTest.TempDir, "containers.conf") + err = os.WriteFile(confFile, []byte("[containers]\nbase_hosts_file=\"none\"\n"), 0755) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF_OVERRIDE", confFile) + if IsRemote() { + podmanTest.RestartRemoteService() + } + + // Hosts file flag should override base_hosts_file in containers.conf + session := podmanTest.Podman([]string{"run", "--rm", "--hosts-file=image", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(ContainSubstring("localhost localhost.localdomain")) + }) + It("podman run with restart-policy always restarts containers", func() { testDir := filepath.Join(podmanTest.RunRoot, "restart-test") err := os.MkdirAll(testDir, 0755)