This adds the option `resolvConf` to the host-local IPAM configuration.
If specified, the plugin will try to parse the file as a resolv.conf(5)
type file and return it in the DNS response.
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block.
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
+* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration
## Supported arguments
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
# host-local IP address manager
-host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range.
+host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. Optionally,
+it can include a DNS configuration from a `resolv.conf` file on the host.
## Usage
"rangeEnd": "3ffe:ffff:0:01ff::0020",
"routes": [
{ "dst": "3ffe:ffff:0:01ff::1/64" }
- ]
+ ],
+ "resolvConf": "/etc/resolv.conf"
}
}
```
Gateway net.IP `json:"gateway"`
Routes []types.Route `json:"routes"`
DataDir string `json:"dataDir"`
+ ResolvConf string `json:"resolvConf"`
Args *IPAMArgs `json:"-"`
}
--- /dev/null
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "os"
+ "strings"
+
+ "github.com/containernetworking/cni/pkg/types"
+)
+
+// parseResolvConf parses an existing resolv.conf in to a DNS struct
+func parseResolvConf(filename string) (*types.DNS, error) {
+ fp, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ dns := types.DNS{}
+ scanner := bufio.NewScanner(fp)
+ for scanner.Scan() {
+ line := scanner.Text()
+ line = strings.TrimSpace(line)
+
+ // Skip comments, empty lines
+ if len(line) == 0 || line[0] == '#' || line[0] == ';' {
+ continue
+ }
+
+ fields := strings.Fields(line)
+ if len(fields) < 2 {
+ continue
+ }
+ switch fields[0] {
+ case "nameserver":
+ dns.Nameservers = append(dns.Nameservers, fields[1])
+ case "domain":
+ dns.Domain = fields[1]
+ case "search":
+ dns.Search = append(dns.Search, fields[1:]...)
+ case "options":
+ dns.Options = append(dns.Options, fields[1:]...)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ return &dns, nil
+}
--- /dev/null
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "io/ioutil"
+ "os"
+
+ "github.com/containernetworking/cni/pkg/types"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("parsing resolv.conf", func() {
+ It("parses a simple resolv.conf file", func() {
+ contents := `
+ nameserver 192.0.2.0
+ nameserver 192.0.2.1
+ `
+ dns, err := parse(contents)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0", "192.0.2.1"}}))
+ })
+ It("ignores comments", func() {
+ dns, err := parse(`
+nameserver 192.0.2.0
+;nameserver 192.0.2.1
+`)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0"}}))
+ })
+ It("parses all fields", func() {
+ dns, err := parse(`
+nameserver 192.0.2.0
+nameserver 192.0.2.2
+domain example.com
+;nameserver comment
+#nameserver comment
+search example.net example.org
+search example.gov
+options one two three
+options four
+`)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(*dns).Should(Equal(types.DNS{
+ Nameservers: []string{"192.0.2.0", "192.0.2.2"},
+ Domain: "example.com",
+ Search: []string{"example.net", "example.org", "example.gov"},
+ Options: []string{"one", "two", "three", "four"},
+ }))
+ })
+})
+
+func parse(contents string) (*types.DNS, error) {
+ f, err := ioutil.TempFile("", "host_local_resolv")
+ defer f.Close()
+ defer os.Remove(f.Name())
+
+ if err != nil {
+ return nil, err
+ }
+
+ if _, err := f.WriteString(contents); err != nil {
+ return nil, err
+ }
+
+ return parseResolvConf(f.Name())
+}
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(tmpDir)
+ err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
+ Expect(err).NotTo(HaveOccurred())
+
conf := fmt.Sprintf(`{
"cniVersion": "0.2.0",
"name": "mynet",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
- "dataDir": "%s"
+ "dataDir": "%s",
+ "resolvConf": "%s/resolv.conf"
}
-}`, tmpDir)
+}`, tmpDir, tmpDir)
args := &skel.CmdArgs{
ContainerID: "dummy",
Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal("10.1.2.2"))
+ Expect(result.DNS).To(Equal(types.DNS{Nameservers: []string{"192.0.2.3"}}))
+
// Release the IP
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
return cmdDel(args)
return err
}
+ r := types.Result{}
+
+ if ipamConf.ResolvConf != "" {
+ dns, err := parseResolvConf(ipamConf.ResolvConf)
+ if err != nil {
+ return err
+ }
+ r.DNS = *dns
+ }
+
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
if err != nil {
return err
if err != nil {
return err
}
+ r.IP4 = ipConf
- r := &types.Result{
- IP4: ipConf,
- }
return r.Print()
}