From a4701b56311d5d934543e2b4306b08baa844ec3f Mon Sep 17 00:00:00 2001 From: baude Date: Fri, 19 Jan 2018 08:51:59 -0600 Subject: Add --dns-search, --dns-opt, --dns-server and --add-host. Each of these options are destructive in nature, meaning if the user adds one of them, all current ones are removed from the produced resolv.conf. * dns-server allows the user to specify dns servers. * dns-opt allows the user to specify special resolv.conf options * dns-search allows the user to specify search domains The add-host option is not destructive and truly just adds the host to /etc/hosts. Signed-off-by: baude Closes: #231 Approved by: mheon --- cmd/podman/create.go | 14 +++++ cmd/podman/spec.go | 13 +++++ libpod/container_api.go | 17 ++---- libpod/container_internal.go | 135 +++++++++++++++++++++++++++++++++++++++++-- libpod/options.go | 53 +++++++++++++++++ 5 files changed, 214 insertions(+), 18 deletions(-) diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 262be129c..55425638f 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -83,6 +83,7 @@ type createConfig struct { Env map[string]string //env ExposedPorts map[nat.Port]struct{} GroupAdd []uint32 // group-add + HostAdd []string //add-host Hostname string //hostname Image string ImageID string @@ -560,6 +561,18 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, networkMode = c.String("net") } + // Verify the additional hosts are in correct format + for _, host := range c.StringSlice("add-host") { + if _, err := validateExtraHost(host); err != nil { + return nil, err + } + } + + // Check for . and dns-search domains + if libpod.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { + return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") + } + config := &createConfig{ Runtime: runtime, CapAdd: c.StringSlice("cap-add"), @@ -576,6 +589,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, ExposedPorts: ports, GroupAdd: groupAdd, Hostname: c.String("hostname"), + HostAdd: c.StringSlice("add-host"), Image: imageName, ImageID: imageID, Interactive: c.Bool("interactive"), diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index 59ea5685a..152d1740c 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -584,6 +584,19 @@ func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, er options = append(options, libpod.WithStopSignal(c.StopSignal)) options = append(options, libpod.WithStopTimeout(c.StopTimeout)) + if len(c.DNSSearch) > 0 { + options = append(options, libpod.WithDNSSearch(c.DNSSearch)) + } + if len(c.DNSServers) > 0 { + options = append(options, libpod.WithDNS(c.DNSServers)) + } + if len(c.DNSOpt) > 0 { + options = append(options, libpod.WithDNSOption(c.DNSOpt)) + } + if len(c.HostAdd) > 0 { + options = append(options, libpod.WithHosts(c.HostAdd)) + } + return options, nil } diff --git a/libpod/container_api.go b/libpod/container_api.go index 9a4f50079..be7fd76bc 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -79,22 +79,15 @@ func (c *Container) Init() (err error) { } // Copy /etc/resolv.conf to the container's rundir - resolvPath := "/etc/resolv.conf" - - // Check if the host system is using system resolve and if so - // copy its resolv.conf - _, err = os.Stat("/run/systemd/resolve/resolv.conf") - if err == nil { - resolvPath = "/run/systemd/resolve/resolv.conf" - } - runDirResolv, err := c.copyHostFileToRundir(resolvPath) + runDirResolv, err := c.generateResolvConf() if err != nil { - return errors.Wrapf(err, "unable to copy resolv.conf to ", runDirResolv) + return err } + // Copy /etc/hosts to the container's rundir - runDirHosts, err := c.copyHostFileToRundir("/etc/hosts") + runDirHosts, err := c.generateHosts() if err != nil { - return errors.Wrapf(err, "unable to copy /etc/hosts to ", runDirHosts) + return errors.Wrapf(err, "unable to copy /etc/hosts to container space") } // Save OCI spec to disk diff --git a/libpod/container_internal.go b/libpod/container_internal.go index a9db43b08..c052f61fe 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -3,8 +3,10 @@ package libpod import ( "fmt" "io" + "io/ioutil" "os" "path/filepath" + "strings" "syscall" "time" @@ -13,7 +15,6 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/stringid" - "github.com/mrunalp/fileutils" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" @@ -373,11 +374,18 @@ func (c *Container) cleanupStorage() error { return c.save() } -// copyHostFileToRundir copies the provided file to the runtimedir -func (c *Container) copyHostFileToRundir(sourcePath string) (string, error) { - destFileName := filepath.Join(c.state.RunDir, filepath.Base(sourcePath)) - if err := fileutils.CopyFile(sourcePath, destFileName); err != nil { - return "", err +// WriteStringToRundir copies the provided file to the runtimedir +func (c *Container) WriteStringToRundir(destFile, output string) (string, error) { + destFileName := filepath.Join(c.state.RunDir, destFile) + f, err := os.Create(destFileName) + if err != nil { + return "", errors.Wrapf(err, "unable to create %s", destFileName) + } + + defer f.Close() + _, err = f.WriteString(output) + if err != nil { + return "", errors.Wrapf(err, "unable to write %s", destFileName) } // Relabel runDirResolv for the container if err := label.Relabel(destFileName, c.config.MountLabel, false); err != nil { @@ -385,3 +393,118 @@ func (c *Container) copyHostFileToRundir(sourcePath string) (string, error) { } return destFileName, nil } + +type resolv struct { + nameServers []string + searchDomains []string + options []string +} + +// generateResolvConf generates a containers resolv.conf +func (c *Container) generateResolvConf() (string, error) { + // Copy /etc/resolv.conf to the container's rundir + resolvPath := "/etc/resolv.conf" + + // Check if the host system is using system resolve and if so + // copy its resolv.conf + if _, err := os.Stat("/run/systemd/resolve/resolv.conf"); err == nil { + resolvPath = "/run/systemd/resolve/resolv.conf" + } + orig, err := ioutil.ReadFile(resolvPath) + if err != nil { + return "", errors.Wrapf(err, "unable to read %s", resolvPath) + } + if len(c.config.DNSServer) == 0 && len(c.config.DNSSearch) == 0 && len(c.config.DNSOption) == 0 { + return c.WriteStringToRundir("resolv.conf", fmt.Sprintf("%s", orig)) + } + + // Read and organize the hosts /etc/resolv.conf + resolv := createResolv(string(orig[:])) + + // Populate the resolv struct with user's dns search domains + if len(c.config.DNSSearch) > 0 { + resolv.searchDomains = nil + // The . character means the user doesnt want any search domains in the container + if !StringInSlice(".", c.config.DNSSearch) { + resolv.searchDomains = append(resolv.searchDomains, c.Config().DNSSearch...) + } + } + + // Populate the resolv struct with user's dns servers + if len(c.config.DNSServer) > 0 { + resolv.nameServers = nil + for _, i := range c.config.DNSServer { + resolv.nameServers = append(resolv.nameServers, i.String()) + } + } + + // Populate the resolve struct with the users dns options + if len(c.config.DNSOption) > 0 { + resolv.options = nil + resolv.options = append(resolv.options, c.Config().DNSOption...) + } + return c.WriteStringToRundir("resolv.conf", resolv.ToString()) +} + +// createResolv creates a resolv struct from an input string +func createResolv(input string) resolv { + var resolv resolv + for _, line := range strings.Split(input, "\n") { + if strings.HasPrefix(line, "search") { + fields := strings.Fields(line) + if len(fields) < 2 { + logrus.Debugf("invalid resolv.conf line %s", line) + continue + } + resolv.searchDomains = append(resolv.searchDomains, fields[1:]...) + } else if strings.HasPrefix(line, "nameserver") { + fields := strings.Fields(line) + if len(fields) < 2 { + logrus.Debugf("invalid resolv.conf line %s", line) + continue + } + resolv.nameServers = append(resolv.nameServers, fields[1]) + } else if strings.HasPrefix(line, "options") { + fields := strings.Fields(line) + if len(fields) < 2 { + logrus.Debugf("invalid resolv.conf line %s", line) + continue + } + resolv.options = append(resolv.options, fields[1:]...) + } + } + return resolv +} + +//ToString returns a resolv struct in the form of a resolv.conf +func (r resolv) ToString() string { + var result string + // Populate the output string with search domains + result += fmt.Sprintf("search %s\n", strings.Join(r.searchDomains, " ")) + // Populate the output string with name servers + for _, i := range r.nameServers { + result += fmt.Sprintf("nameserver %s\n", i) + } + // Populate the output string with dns options + for _, i := range r.options { + result += fmt.Sprintf("options %s\n", i) + } + return result +} + +// generateHosts creates a containers hosts file +func (c *Container) generateHosts() (string, error) { + orig, err := ioutil.ReadFile("/etc/hosts") + if err != nil { + return "", errors.Wrapf(err, "unable to read /etc/hosts") + } + hosts := string(orig) + if len(c.config.HostAdd) > 0 { + for _, host := range c.config.HostAdd { + // the host format has already been verified at this point + fields := strings.Split(host, ":") + hosts += fmt.Sprintf("%s %s\n", fields[0], fields[1]) + } + } + return c.WriteStringToRundir("hosts", hosts) +} diff --git a/libpod/options.go b/libpod/options.go index ca4d104df..63a72628f 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1,6 +1,7 @@ package libpod import ( + "net" "path/filepath" "regexp" "syscall" @@ -641,3 +642,55 @@ func WithPodLabels(labels map[string]string) PodCreateOption { return nil } } + +// WithDNSSearch sets the additional search domains of a container +func WithDNSSearch(searchDomains []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.DNSSearch = searchDomains + return nil + } +} + +// WithDNS sets additional name servers for the container +func WithDNS(dnsServers []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + var dns []net.IP + for _, i := range dnsServers { + result := net.ParseIP(i) + if result == nil { + return errors.Wrapf(ErrInvalidArg, "invalid IP address %s", i) + } + dns = append(dns, result) + } + ctr.config.DNSServer = dns + return nil + } +} + +// WithDNSOption sets addition dns options for the container +func WithDNSOption(dnsOptions []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.DNSOption = dnsOptions + return nil + } +} + +// WithHosts sets additional host:IP for the hosts file +func WithHosts(hosts []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.HostAdd = hosts + return nil + } +} -- cgit v1.2.3-54-g00ecf