ipam/host-local: add ResolvConf argument for DNS configuration
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.
This commit is contained in:

committed by
Casey Callendrello

parent
4406607649
commit
5cde14cd7b
@ -30,6 +30,7 @@ It stores the state locally on the host filesystem, therefore ensuring uniquenes
|
|||||||
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block.
|
* `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.
|
* `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.
|
* `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
|
## Supported arguments
|
||||||
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# host-local IP address manager
|
# 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
|
## Usage
|
||||||
|
|
||||||
@ -65,7 +66,8 @@ f81d4fae-7dec-11d0-a765-00a0c91e6bf6
|
|||||||
"rangeEnd": "3ffe:ffff:0:01ff::0020",
|
"rangeEnd": "3ffe:ffff:0:01ff::0020",
|
||||||
"routes": [
|
"routes": [
|
||||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||||
]
|
],
|
||||||
|
"resolvConf": "/etc/resolv.conf"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -32,6 +32,7 @@ type IPAMConfig struct {
|
|||||||
Gateway net.IP `json:"gateway"`
|
Gateway net.IP `json:"gateway"`
|
||||||
Routes []types.Route `json:"routes"`
|
Routes []types.Route `json:"routes"`
|
||||||
DataDir string `json:"dataDir"`
|
DataDir string `json:"dataDir"`
|
||||||
|
ResolvConf string `json:"resolvConf"`
|
||||||
Args *IPAMArgs `json:"-"`
|
Args *IPAMArgs `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
plugins/ipam/host-local/dns.go
Normal file
64
plugins/ipam/host-local/dns.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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
|
||||||
|
}
|
80
plugins/ipam/host-local/dns_test.go
Normal file
80
plugins/ipam/host-local/dns_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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())
|
||||||
|
}
|
@ -38,6 +38,9 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
defer os.RemoveAll(tmpDir)
|
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(`{
|
conf := fmt.Sprintf(`{
|
||||||
"cniVersion": "0.2.0",
|
"cniVersion": "0.2.0",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
@ -46,9 +49,10 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"dataDir": "%s"
|
"dataDir": "%s",
|
||||||
|
"resolvConf": "%s/resolv.conf"
|
||||||
}
|
}
|
||||||
}`, tmpDir)
|
}`, tmpDir, tmpDir)
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
args := &skel.CmdArgs{
|
||||||
ContainerID: "dummy",
|
ContainerID: "dummy",
|
||||||
@ -80,6 +84,8 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("10.1.2.2"))
|
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
|
// Release the IP
|
||||||
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
err = testutils.CmdDelWithResult(nspath, ifname, func() error {
|
||||||
return cmdDel(args)
|
return cmdDel(args)
|
||||||
|
@ -33,6 +33,16 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
return err
|
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)
|
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -48,10 +58,8 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
r.IP4 = ipConf
|
||||||
|
|
||||||
r := &types.Result{
|
|
||||||
IP4: ipConf,
|
|
||||||
}
|
|
||||||
return r.Print()
|
return r.Print()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user