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 <bbaude@redhat.com>

Closes: #231
Approved by: mheon
This commit is contained in:
baude 2018-01-19 08:51:59 -06:00 committed by Atomic Bot
parent 1710acd18a
commit a4701b5631
5 changed files with 215 additions and 19 deletions

View File

@ -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"),

View File

@ -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
}

View File

@ -79,22 +79,15 @@ func (c *Container) Init() (err error) {
}
// Copy /etc/resolv.conf to the container's rundir
resolvPath := "/etc/resolv.conf"
runDirResolv, err := c.generateResolvConf()
if err != nil {
return err
}
// 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)
if err != nil {
return errors.Wrapf(err, "unable to copy resolv.conf to ", runDirResolv)
}
// 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

View File

@ -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)
}

View File

@ -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
}
}