From 4f7395f93aaf8bbc4d1df3dcf2e1e06b6bab8375 Mon Sep 17 00:00:00 2001 From: Gavin Lam Date: Mon, 18 Nov 2024 12:02:02 -0500 Subject: [PATCH] Add --hosts-file flag to container and pod commands * Add --hosts-file flag to container create, container run and pod create * Add HostsFile field to pod inspect and container inspect results * Test BaseHostsFile config in containers.conf Signed-off-by: Gavin Lam --- cmd/podman/common/completion.go | 7 + cmd/podman/common/netflags.go | 14 ++ cmd/podman/containers/create.go | 3 + cmd/podman/pods/create.go | 1 + docs/source/markdown/options/hosts-file.md | 12 ++ docs/source/markdown/podman-create.1.md.in | 2 + .../source/markdown/podman-pod-create.1.md.in | 2 + docs/source/markdown/podman-run.1.md.in | 2 + libpod/container_config.go | 9 +- libpod/container_inspect.go | 2 + libpod/define/container_inspect.go | 2 + libpod/define/pod_inspect.go | 3 + libpod/pod_api.go | 3 + pkg/domain/entities/pods.go | 1 + pkg/domain/entities/types.go | 1 + pkg/specgen/generate/pod_create.go | 3 + pkg/specgen/pod_validate.go | 12 +- pkg/specgen/podspecgen.go | 7 + pkg/specgen/specgen.go | 9 +- pkg/specgenutil/specgen.go | 1 + test/e2e/common_test.go | 13 +- test/e2e/containers_conf_test.go | 77 ++++++++++- test/e2e/pod_create_test.go | 124 ++++++++++++++++++ test/e2e/run_test.go | 106 +++++++++++++++ 24 files changed, 398 insertions(+), 18 deletions(-) create mode 100644 docs/source/markdown/options/hosts-file.md 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..4d0cb5266b 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, "", + `Base file to create the /etc/hosts file inside the container, or one of the special values. ("image"|"none")`, + ) + _ = cmd.RegisterFlagCompletionFunc(hostsFileFlagName, AutocompleteHostsFile) + dnsFlagName := "dns" netFlags.StringSlice( dnsFlagName, podmanConfig.ContainersConf.DNSServers(), @@ -116,6 +123,13 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti } } + if flags.Changed("hosts-file") { + opts.HostsFile, err = flags.GetString("hosts-file") + if err != nil { + return nil, err + } + } + if flags.Changed("dns") { servers, err := flags.GetStringSlice("dns") if err != nil { diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 15e714b342..547911404c 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -323,6 +323,9 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra if noHosts && c.Flag("add-host").Changed { return vals, errors.New("--no-hosts and --add-host cannot be set together") } + if noHosts && c.Flag("hosts-file").Changed { + return vals, errors.New("--no-hosts and --hosts-file cannot be set together") + } if !isInfra && c.Flag("entrypoint").Changed { val := c.Flag("entrypoint").Value.String() diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index e6fd898db4..91c3328977 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -265,6 +265,7 @@ func create(cmd *cobra.Command, args []string) error { } podSpec.InfraContainerSpec = specgen.NewSpecGenerator(imageName, false) podSpec.InfraContainerSpec.RawImageName = rawImageName + podSpec.InfraContainerSpec.BaseHostsFile = podSpec.PodNetworkConfig.HostsFile podSpec.InfraContainerSpec.NetworkOptions = podSpec.NetworkOptions podSpec.InfraContainerSpec.RestartPolicy = podSpec.RestartPolicy err = specgenutil.FillOutSpecGen(podSpec.InfraContainerSpec, &infraOptions, []string{}) 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/libpod/container_config.go b/libpod/container_config.go index 5ed80382a0..0f6f8959b0 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -290,11 +290,10 @@ type ContainerNetworkConfig struct { // bind-mounted inside the container. // Conflicts with HostAdd. UseImageHosts bool - // BaseHostsFile is the path to a hosts file, the entries from this file - // are added to the containers hosts file. As special value "image" is - // allowed which uses the /etc/hosts file from within the image and "none" - // which uses no base file at all. If it is empty we should default - // to the base_hosts_file configuration in containers.conf. + // BaseHostsFile is the 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 + // special flags `image` or `none`. + // If it is empty it defaults to the base_hosts_file configuration in containers.conf. BaseHostsFile string `json:"baseHostsFile,omitempty"` // Hosts to add in container // Will be appended to host's host file diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index a94175407f..55d5dea477 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -543,6 +543,8 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named hostConfig.GroupAdd = make([]string, 0, len(c.config.Groups)) hostConfig.GroupAdd = append(hostConfig.GroupAdd, c.config.Groups...) + hostConfig.HostsFile = c.config.BaseHostsFile + if ctrSpec.Process != nil { if ctrSpec.Process.OOMScoreAdj != nil { hostConfig.OomScoreAdj = *ctrSpec.Process.OOMScoreAdj diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index b4352554b1..3c842cb101 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -443,6 +443,8 @@ type InspectContainerHostConfig struct { // ExtraHosts contains hosts that will be added to the container's // /etc/hosts. ExtraHosts []string `json:"ExtraHosts"` + // HostsFile is the base file to create the `/etc/hosts` file inside the container. + HostsFile string `json:"HostsFile"` // GroupAdd contains groups that the user inside the container will be // added to. GroupAdd []string `json:"GroupAdd"` diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go index bcd34f15a1..23b0326fb4 100644 --- a/libpod/define/pod_inspect.go +++ b/libpod/define/pod_inspect.go @@ -124,6 +124,9 @@ type InspectPodInfraConfig struct { // HostAdd adds a number of hosts to the infra container's resolv.conf // which will be shared with the rest of the pod. HostAdd []string + // HostsFile is the base file to create the `/etc/hosts` file inside the infra container + // which will be shared with the rest of the pod. + HostsFile string // Networks is a list of networks the pod will join. Networks []string // NetworkOptions are additional options for each network diff --git a/libpod/pod_api.go b/libpod/pod_api.go index f0effef844..53c1024317 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -709,6 +709,9 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { infraConfig.HostAdd = make([]string, 0, len(infra.config.HostAdd)) infraConfig.HostAdd = append(infraConfig.HostAdd, infra.config.HostAdd...) } + if len(infra.config.BaseHostsFile) > 0 { + infraConfig.HostsFile = infra.config.BaseHostsFile + } networks, err := infra.networks() if err != nil { diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 96c4a3bf66..2648b6716d 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -368,6 +368,7 @@ func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.Pod s.DNSOption = p.Net.DNSOptions s.NoManageHosts = p.Net.NoHosts s.HostAdd = p.Net.AddHosts + s.HostsFile = p.Net.HostsFile } // Cgroup diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 277460ed75..13e779ebad 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -55,6 +55,7 @@ type NetOptions struct { DNSOptions []string `json:"dns_option,omitempty"` DNSSearch []string `json:"dns_search,omitempty"` DNSServers []net.IP `json:"dns_server,omitempty"` + HostsFile string `json:"hosts_file,omitempty"` Network specgen.Namespace `json:"netns,omitempty"` NoHosts bool `json:"no_manage_hosts,omitempty"` PublishPorts []types.PortMapping `json:"portmappings,omitempty"` diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 9b01a747e0..186aba746b 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -243,6 +243,9 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) { if len(p.HostAdd) > 0 { spec.HostAdd = p.HostAdd } + if len(p.HostsFile) > 0 { + spec.BaseHostsFile = p.HostsFile + } if len(p.DNSServer) > 0 { var dnsServers []net.IP dnsServers = append(dnsServers, p.DNSServer...) diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 551f9eb18a..a2732eeb23 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -58,6 +58,9 @@ func (p *PodSpecGenerator) Validate() error { if len(p.HostAdd) > 0 { return exclusivePodOptions("NoInfra", "HostAdd") } + if len(p.HostsFile) > 0 { + return exclusivePodOptions("NoInfra", "HostsFile") + } if p.NoManageResolvConf { return exclusivePodOptions("NoInfra", "NoManageResolvConf") } @@ -79,8 +82,13 @@ func (p *PodSpecGenerator) Validate() error { return exclusivePodOptions("NoManageResolvConf", "DNSOption") } } - if p.NoManageHosts && len(p.HostAdd) > 0 { - return exclusivePodOptions("NoManageHosts", "HostAdd") + if p.NoManageHosts { + if len(p.HostAdd) > 0 { + return exclusivePodOptions("NoManageHosts", "HostAdd") + } + if len(p.HostsFile) > 0 { + return exclusivePodOptions("NoManageHosts", "HostsFile") + } } return nil diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 5c8e561ecc..7f22ed7ad5 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -170,6 +170,13 @@ type PodNetworkConfig struct { // Conflicts with NoInfra=true and NoManageHosts. // Optional. HostAdd []string `json:"hostadd,omitempty"` + // HostsFile is the base file to create the `/etc/hosts` file inside the infra container. + // This must either be an absolute path to a file on the host system, or one of the + // special flags `image` or `none`. + // If it is empty it defaults to the base_hosts_file configuration in containers.conf. + // Conflicts with NoInfra=true and NoManageHosts. + // Optional. + HostsFile string `json:"hostsFile,omitempty"` // NetworkOptions are additional options for each network // Optional. NetworkOptions map[string][]string `json:"network_options,omitempty"` diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index eca50d6cf7..7bb5bc4a86 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -539,11 +539,10 @@ type ContainerNetworkConfig struct { // Conflicts with HostAdd. // Optional. UseImageHosts *bool `json:"use_image_hosts,omitempty"` - // BaseHostsFile is the path to a hosts file, the entries from this file - // are added to the containers hosts file. As special value "image" is - // allowed which uses the /etc/hosts file from within the image and "none" - // which uses no base file at all. If it is empty we should default - // to the base_hosts_file configuration in containers.conf. + // BaseHostsFile is the 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 + // special flags `image` or `none`. + // If it is empty it defaults to the base_hosts_file configuration in containers.conf. // Optional. BaseHostsFile string `json:"base_hosts_file,omitempty"` // HostAdd is a set of hosts which will be added to the container's diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 1c76acaff9..79a8f66ac1 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -587,6 +587,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if c.Net != nil { s.HostAdd = c.Net.AddHosts + s.BaseHostsFile = c.Net.HostsFile s.UseImageResolvConf = &c.Net.UseImageResolvConf s.DNSServers = c.Net.DNSServers s.DNSSearch = c.Net.DNSSearch diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 089aaf5813..f4f5766116 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -663,14 +663,14 @@ func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSe // BuildImage uses podman build and buildah to build an image // called imageName based on a string dockerfile -func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) string { - return p.buildImage(dockerfile, imageName, layers, "") +func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string, extraOptions ...string) string { + return p.buildImage(dockerfile, imageName, layers, "", extraOptions) } // BuildImageWithLabel uses podman build and buildah to build an image // called imageName based on a string dockerfile, adds desired label to paramset -func (p *PodmanTestIntegration) BuildImageWithLabel(dockerfile, imageName string, layers string, label string) string { - return p.buildImage(dockerfile, imageName, layers, label) +func (p *PodmanTestIntegration) BuildImageWithLabel(dockerfile, imageName string, layers string, label string, extraOptions ...string) string { + return p.buildImage(dockerfile, imageName, layers, label, extraOptions) } // PodmanPID execs podman and returns its PID @@ -1299,7 +1299,7 @@ func (s *PodmanSessionIntegration) jq(jqCommand string) (string, error) { return strings.TrimRight(out.String(), "\n"), err } -func (p *PodmanTestIntegration) buildImage(dockerfile, imageName string, layers string, label string) string { +func (p *PodmanTestIntegration) buildImage(dockerfile, imageName string, layers string, label string, extraOptions []string) string { dockerfilePath := filepath.Join(p.TempDir, "Dockerfile-"+stringid.GenerateRandomID()) err := os.WriteFile(dockerfilePath, []byte(dockerfile), 0755) Expect(err).ToNot(HaveOccurred()) @@ -1310,6 +1310,9 @@ func (p *PodmanTestIntegration) buildImage(dockerfile, imageName string, layers if len(imageName) > 0 { cmd = append(cmd, []string{"-t", imageName}...) } + if len(extraOptions) > 0 { + cmd = append(cmd, extraOptions...) + } cmd = append(cmd, p.TempDir) session := p.Podman(cmd) session.Wait(240) diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 94e61468c3..e86db89622 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -21,7 +21,9 @@ import ( var _ = Describe("Verify podman containers.conf usage", func() { BeforeEach(func() { - os.Setenv("CONTAINERS_CONF", "config/containers.conf") + confPath, err := filepath.Abs("config/containers.conf") + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF", confPath) if IsRemote() { podmanTest.RestartRemoteService() } @@ -463,6 +465,79 @@ 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() + } + + dockerfile := strings.Join([]string{ + `FROM quay.io/libpod/alpine:latest`, + `RUN echo '56.78.12.34 image.example.com' > /etc/hosts`, + }, "\n") + podmanTest.BuildImage(dockerfile, "foobar.com/hosts_test:latest", "false", "--no-hosts") + + session = podmanTest.Podman([]string{"run", "--name", "hosts_test", "--hostname", "hosts_test.dev", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + }) + + Describe("base_hosts_file=path", func() { + BeforeEach(func() { + hostsPath := filepath.Join(podmanTest.TempDir, "hosts") + err := os.WriteFile(hostsPath, []byte("12.34.56.78 file.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + baseHostsFile = hostsPath + }) + + It("should use the hosts file from the file path", func() { + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("12.34.56.78 file.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + }) + + 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("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 file.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + }) + + Describe("base_hosts_file=none", func() { + BeforeEach(func() { + baseHostsFile = "none" + }) + + It("should not use any hosts files", func() { + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 file.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + }) + }) + 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..349994d203 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -149,6 +149,130 @@ 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 --add-host and --no-hosts should fail", func() { + name := "test" + podCreate := podmanTest.Podman([]string{"pod", "create", "--add-host", "test.example.com:12.34.56.78", "--name", name, "--no-hosts"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitWithError(125, "--no-hosts and --add-host cannot be set together")) + }) + + Describe("podman create pod with --hosts-file", func() { + BeforeEach(func() { + imageHosts := filepath.Join(podmanTest.TempDir, "pause_hosts") + err := os.WriteFile(imageHosts, []byte("56.78.12.34 image.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + configHosts := filepath.Join(podmanTest.TempDir, "hosts") + err = os.WriteFile(configHosts, []byte("12.34.56.78 config.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + confFile := filepath.Join(podmanTest.TempDir, "containers.conf") + err = os.WriteFile(confFile, []byte(fmt.Sprintf("[containers]\nbase_hosts_file=\"%s\"\n", configHosts)), 0755) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF_OVERRIDE", confFile) + if IsRemote() { + podmanTest.RestartRemoteService() + } + + dockerfile := strings.Join([]string{ + `FROM ` + INFRA_IMAGE, + `COPY pause_hosts /etc/hosts`, + }, "\n") + podmanTest.BuildImage(dockerfile, "foobar.com/hosts_test_pause:latest", "false", "--no-hosts") + }) + + It("--hosts-file=path", func() { + hostsPath := filepath.Join(podmanTest.TempDir, "hosts") + err := os.WriteFile(hostsPath, []byte("23.45.67.89 file.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + podCreate := podmanTest.Podman([]string{"pod", "create", "--hostname", "hosts_test.dev", "--hosts-file=" + hostsPath, "--add-host=add.example.com:34.56.78.90", "--infra-image=foobar.com/hosts_test_pause:latest", "--infra-name=hosts_test_infra", "--name", "hosts_test_pod"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitCleanly()) + + session := podmanTest.Podman([]string{"run", "--pod", "hosts_test_pod", "--name", "hosts_test", "--rm", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("23.45.67.89 file.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test_infra")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 hosts_test")) + }) + + It("--hosts-file=image", func() { + podCreate := podmanTest.Podman([]string{"pod", "create", "--hostname", "hosts_test.dev", "--hosts-file=image", "--add-host=add.example.com:34.56.78.90", "--infra-image=foobar.com/hosts_test_pause:latest", "--infra-name=hosts_test_infra", "--name", "hosts_test_pod"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitCleanly()) + + session := podmanTest.Podman([]string{"run", "--pod", "hosts_test_pod", "--name", "hosts_test", "--rm", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test_infra")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 hosts_test")) + }) + + It("--hosts-file=none", func() { + podCreate := podmanTest.Podman([]string{"pod", "create", "--hostname", "hosts_test.dev", "--hosts-file=none", "--add-host=add.example.com:34.56.78.90", "--infra-image=foobar.com/hosts_test_pause:latest", "--infra-name=hosts_test_infra", "--name", "hosts_test_pod"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitCleanly()) + + session := podmanTest.Podman([]string{"run", "--pod", "hosts_test_pod", "--name", "hosts_test", "--rm", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test_infra")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 hosts_test")) + }) + + It("--hosts-file= falls back to containers.conf", func() { + podCreate := podmanTest.Podman([]string{"pod", "create", "--hostname", "hosts_test.dev", "--hosts-file=", "--add-host=add.example.com:34.56.78.90", "--infra-image=foobar.com/hosts_test_pause:latest", "--infra-name=hosts_test_infra", "--name", "hosts_test_pod"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitCleanly()) + + session := podmanTest.Podman([]string{"run", "--pod", "hosts_test_pod", "--name", "hosts_test", "--rm", ALPINE, "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test_infra")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 hosts_test")) + }) + }) + + It("podman create pod with --hosts-file and no infra should fail", func() { + name := "test" + podCreate := podmanTest.Podman([]string{"pod", "create", "--hosts-file=image", "--name", name, "--infra=false"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitWithError(125, "NoInfra and HostsFile are mutually exclusive pod options: invalid pod spec")) + }) + + It("podman create pod with --hosts-file and --no-hosts should fail", func() { + name := "test" + podCreate := podmanTest.Podman([]string{"pod", "create", "--hosts-file=image", "--name", name, "--no-hosts"}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(ExitWithError(125, "--no-hosts and --hosts-file cannot be set together")) + }) + 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 478e34ca7f..ee7147feaf 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1504,6 +1504,112 @@ VOLUME %s`, ALPINE, volPath, volPath) Expect(session).To(ExitWithError(125, "--no-hosts and --add-host cannot be set together")) }) + Describe("podman run with --hosts-file", func() { + BeforeEach(func() { + configHosts := filepath.Join(podmanTest.TempDir, "hosts") + err := os.WriteFile(configHosts, []byte("12.34.56.78 config.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + confFile := filepath.Join(podmanTest.TempDir, "containers.conf") + err = os.WriteFile(confFile, []byte(fmt.Sprintf("[containers]\nbase_hosts_file=\"%s\"\n", configHosts)), 0755) + Expect(err).ToNot(HaveOccurred()) + os.Setenv("CONTAINERS_CONF_OVERRIDE", confFile) + if IsRemote() { + podmanTest.RestartRemoteService() + } + + dockerfile := strings.Join([]string{ + `FROM quay.io/libpod/alpine:latest`, + `RUN echo '56.78.12.34 image.example.com' > /etc/hosts`, + }, "\n") + podmanTest.BuildImage(dockerfile, "foobar.com/hosts_test:latest", "false", "--no-hosts") + }) + + It("--hosts-file=path", func() { + hostsPath := filepath.Join(podmanTest.TempDir, "hosts") + err := os.WriteFile(hostsPath, []byte("23.45.67.89 file.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + session := podmanTest.Podman([]string{"run", "--hostname", "hosts_test.dev", "--hosts-file=" + hostsPath, "--add-host=add.example.com:34.56.78.90", "--name", "hosts_test", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("23.45.67.89 file.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + + It("--hosts-file=image", func() { + session := podmanTest.Podman([]string{"run", "--hostname", "hosts_test.dev", "--hosts-file=image", "--add-host=add.example.com:34.56.78.90", "--name", "hosts_test", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + + It("--hosts-file=none", func() { + session := podmanTest.Podman([]string{"run", "--hostname", "hosts_test.dev", "--hosts-file=none", "--add-host=add.example.com:34.56.78.90", "--name", "hosts_test", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + + It("--hosts-file= falls back to containers.conf", func() { + session := podmanTest.Podman([]string{"run", "--hostname", "hosts_test.dev", "--hosts-file=", "--add-host=add.example.com:34.56.78.90", "--name", "hosts_test", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).ToNot(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + + It("works with pod without an infra-container", func() { + _, ec, _ := podmanTest.CreatePod(map[string][]string{"--name": {"hosts_test_pod"}}) + Expect(ec).To(Equal(0)) + + session := podmanTest.Podman([]string{"run", "--pod", "hosts_test_pod", "--hostname", "hosts_test.dev", "--hosts-file=image", "--add-host=add.example.com:34.56.78.90", "--name", "hosts_test", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(ContainSubstring("56.78.12.34 image.example.com")) + Expect(session.OutputToString()).ToNot(ContainSubstring("12.34.56.78 config.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("34.56.78.90 add.example.com")) + Expect(session.OutputToString()).To(ContainSubstring("127.0.0.1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("::1 localhost")) + Expect(session.OutputToString()).To(ContainSubstring("host.containers.internal host.docker.internal")) + Expect(session.OutputToString()).To(ContainSubstring("hosts_test.dev hosts_test")) + }) + + It("should fail with --no-hosts", func() { + hostsPath := filepath.Join(podmanTest.TempDir, "hosts") + err := os.WriteFile(hostsPath, []byte("23.45.67.89 file2.example.com"), 0755) + Expect(err).ToNot(HaveOccurred()) + + session := podmanTest.Podman([]string{"run", "--no-hosts", "--hosts-file=" + hostsPath, "--name", "hosts_test", "--rm", "foobar.com/hosts_test:latest", "cat", "/etc/hosts"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError(125, "--no-hosts and --hosts-file cannot be set together")) + }) + + }) + It("podman run with restart-policy always restarts containers", func() { testDir := filepath.Join(podmanTest.RunRoot, "restart-test") err := os.MkdirAll(testDir, 0755)