docs: cleanup (#167)

This cleans up and pulls some things in the modern age. Remove text,
tweak other bits. Make it easier to consume.

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben
2019-09-28 07:23:58 +01:00
committed by GitHub
parent 0c6ef34234
commit 5e8623c35e
4 changed files with 79 additions and 138 deletions
+40 -92
View File
@@ -6,48 +6,37 @@ title = "Writing Plugins for CoreDNS"
author = "miek"
+++
As CoreDNS uses Caddy for setting up and using plugin, the process of writing plugin is
remarkably [similar](https://github.com/mholt/caddy/wiki/Writing-a-Plugin:-Directives). This post
slightly reworks (and simplifies in some cases) those pages.
A new plugin adds new functionality to CoreDNS, i.e. *caching*, *metrics* and basic *zone* file
serving are all plugins.
A plugin adds functionality to CoreDNS, i.e. *caching*, *metrics* and basic *zone* file serving are
all plugins.
If you want to write a new plugin and want it to be included by default, i.e. merged in the code
base please open an [issue](https://github.com/coredns/coredns/issues) first to discuss initial design
and other things that may come up.
base please open an [issue](https://github.com/coredns/coredns/issues) first to discuss initial
design and other things that may come up. Starting with a README file to explain how things work
from a user perspective is usually a good idea.
## How to Register a CoreDNS Plugin
See the [example plugin](https://github.com/coredns/example) for, uh, an example for how to
structure, write and test a plugin. There are plenty of comments in the code to help you along.
[Caddy](https://caddyserver.com) supports different type of plugins, CoreDNS, for instance,
registers itself as a Caddy server type plugin. CoreDNS currently supports one type of plugins;
a *plugin*.
## How to Register a CoreDNS Plugin?
Start a new Go package with an init function. Then register your plugin:
When writing your plugin code you will need to register it with CoreDNS. This can be done by calling
the following function:
```go
import "github.com/mholt/caddy"
func init() {
caddy.RegisterPlugin("foo", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func init() { plugin.Register("foo", setup) }
```
Every plugin must have a name, `foo`, in this case. The *ServerType* must be `dns`. The *Action*
speficied here is to say it will call a function called `setup` whenever the *directive* `foo` is
encountered in the Corefile.
Every plugin must have a name, `foo`, in this case. When `foo` is encountered in the configuration
the `setup` function will be called in this package.
### The Setup Function
The *Action* field of the `caddy.Plugin` struct is what makes a directive plugin unique. This is the
function to run when CoreDNS is parsing and executing the Corefile.
The `setup` function (it may be called different, but pretty much every plugin just calls it
`setup`) parses the configuration and populates internal data structures.
The action is simply a function that takes a caddy.Controller and returns an error:
(We use [plugin.Error](https://godoc.org/github.com/coredns/coredns/plugin#Error) to prefix
returned error with `plugin/foo: ` to improve error reporting).
The setup function a `caddy.Controller` and returns an error: (We use
[plugin.Error](https://godoc.org/github.com/coredns/coredns/plugin#Error) to prefix returned error
with `plugin/foo:` to improve error reporting).
``` go
func setup(c *caddy.Controller) error {
@@ -55,50 +44,38 @@ func setup(c *caddy.Controller) error {
return plugin.Error("foo", err)
}
// various other code
return nil
}
```
It is the responsibility of the setup function to parse the directive's tokens and configure itself.
The [Controller struct](https://godoc.org/github.com/mholt/caddy#Controller)
makes this quite easy. If we expect a line in the Corefile such as:
If we see a line in the Corefile such as:
```
foo gizmo
```
We can get the value of the first argument ("foobar") like so:
We can get the value of the first argument ("gizmo") like so:
```go
for c.Next() { // Skip the directive name.
for c.Next() { // Skip the plugin name, "foo" in this case.
if !c.NextArg() { // Expect at least one value.
return c.ArgErr() // Otherwise it's an error.
}
value := c.Val() // Use the value.
}
```
You parse the tokens present for your directive by iterating over `c.Next()` which is true as long
as there are more tokens to parse. Since a directive may appear multiple times, you must iterate
over `c.Next()` to get all the appearances of your directive and consume the first token (which is the
directive name).
You parse the tokens present for your plugin by iterating over `c.Next()` which is true as long
as there are more tokens to parse. Since a plugin may appear multiple times, you must iterate over
`c.Next()` to get all the appearances of your plugin and consume the tokens.
### Adding to CoreDNS
To plug your plugin into CoreDNS, import it. This is done in
[core/coredns.go](https://github.com/coredns/coredns/blob/master/core/coredns.go):
To plug your plugin into CoreDNS, put in
[`plugin.cfg`](https://github.com/coredns/coredns/blob/master/plugin.cfg) and run `go generate`.
```go
import _ "your/plugin/package/path/here"
```
This makes CoreDNS compile your plugin, but it is still not available, so the second step is
to add it to [directives.go](https://github.com/coredns/coredns/blob/master/core/dnsserver/directives.go):
Add the name (`foo`) of your plugin at the end of the file in the `directives` string slice.
Note the ordering is important, because this is determines how the plugins are chained together.
## How DNS Plugin Works in CoreDNS
## How A Plugin Works in CoreDNS
Check out the [godoc for the plugin
package](http://godoc.org/github.com/coredns/coredns/plugin). The most important type is
@@ -110,9 +87,8 @@ up an DNS server for you, but you need to implement these two types.
### Writing a Handler
`plugin.Handler` is an interface similar to `http.Handler` except that it deals with DNS and the
`ServeDNS` method returns `(int, error)`. The `int` is the DNS rcode, and the `error` is one that
should be handled and/or logged. Read the
[plugin.md](https://github.com/coredns/coredns/blob/master/plugin.md) doc for more details
`ServeDNS` method returns `(int, error)`. The `int` is status code, and the `error` is logged (if
not nil) See [plugin.md](https://github.com/coredns/coredns/blob/master/plugin.md) for more details
about these return values.
Handlers are usually a struct with at least one field, the next Handler in the chain:
@@ -123,9 +99,9 @@ type MyHandler struct {
}
```
To implement the `plugin.Handler` interface, we write a method called `ServeDNS`.
This method is the actual handler function, and, unless it fully handles the request by itself, it
should call the next handler in the chain:
To implement the `plugin.Handler` interface, we write a method called `ServeDNS`. This method is the
actual handler function, and, unless it fully handles the request by itself, it should call the next
handler in the chain:
```go
func (h MyHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
@@ -133,46 +109,16 @@ func (h MyHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
}
```
The interface also needs a method `func Name() string`, this is mainly used to see if an plugin
is active, i.e. the *auto* plugin used this to detect if *metrics* are active and if so, adds
updates the zone info.
The interface also needs a method `func Name() string`.,
```go
func (h MyHandler) Name() string { return "foo" }
```
That's all there is to it.
That's all there is to it (apart from writing all code that actually does something with the DNS
request of course).
## How to Add the Handler to CoreDNS
So, back in your setup function. You've just parsed the tokens and set up your plugin handler
with all the proper configuration:
```go
func setup(c *caddy.Controller) error {
for c.Next() {
// Get configuration.
}
// Now what?
}
```
To chain in your new handler, get the config for the current site from the dnsserver package.
Then wrap your handler in a plugin function:
```go
cfg := dnsserver.GetConfig(c)
mid := func(next plugin.Handler) plugin.Handler {
return MyHandler{Next: next}
}
cfg.AddPlugin(mid)
```
And you're done! Of course, in this example, we simply allocated a `MyHandler` with no special
configuration. It doesn't really matter as long as you chain in the `next` handler properly!
## Examples
## Further Reading
Simple examples of plugin that can be found in CoreDNS are:
@@ -180,6 +126,8 @@ Simple examples of plugin that can be found in CoreDNS are:
a plugin. It simply performs some setup.
* [chaos](https://godoc.org/github.com/coredns/coredns/plugin/chaos); a DNS plugin that
responds to `CH txt version.bind` requests.
* [example](https://github.com/coredns/example); an example plugin that prints "example" when
responding to a query.
**Don't forget: the best documentation is the [godoc](https://godoc.org/github.com/coredns/coredns)
and the [code](https://github.com/coredns/coredns) itself!**
+4 -5
View File
@@ -10,8 +10,7 @@ author = "miek"
CoreDNS' [plugins](/plugins) (or [external plugins](/explugins)) can be enabled or
disabled on the fly by specifying (or not specifying) it in the
[Corefile](/2017/07/23/corefile-explained/). But you can also compile CoreDNS with only the
plugin you *need* and leave the rest completely out.
plugins you *need* and leave the rest completely out.
There are two ways to achieve that. It could be done via compile-time configuration file
with CoreDNS code base update. It also could be achieved without modifying CoreDNS code.
@@ -26,7 +25,7 @@ to update. It looks like this:
...
whoami:whoami
erratic:erratic
startup:github.com/mholt/caddy/startupshutdown
federation:github.com/coredns/federation
...
~~~
@@ -82,5 +81,5 @@ The directives should also be updated through:
The ordering of the plugins is specified by how the arey ordered in the slice `directives`.
Then you can just compile CoreDNS with `go build` to have the binary generated with the
plugins you selected.
Then you can just compile CoreDNS with `go build` to have the binary generated with the␍ plugins you
selected.
+28 -36
View File
@@ -42,22 +42,13 @@ Typically a plugin has a file called `setup.go` that handles the registration.
In there, the `init` function should look like:
~~~ go
func init() {
caddy.RegisterPlugin("whoami", caddy.Plugin{
ServerType: "dns",
Action: setupWhoami,
})
}
func init() { plugin.Register("whoami", setup) }
~~~
First you see that we're using `caddy.RegisterPlugin` which makes perfectly sense, because
CoreDNS uses the [Caddy](https://caddyserver.com) server framework. The first argument is the name
of the *plugin* as used in the Corefile.
The `ServerType` is "dns" because CoreDNS is a DNS server; Caddy supports HTTP servers as well. The
`Action` is the name of the setup function that takes care of the parsing of the Corefile. Its job
`setup` is the name of the setup function that takes care of the parsing of the Corefile. Its job
is to return a type that implements the `plugin.Handler` interface.
So whenever the Corefile parser sees "whoami", `setupWhoami` is called.
So whenever the Corefile parser sees "whoami", `whoami.setup` is called.
# 2. Setup function
@@ -79,7 +70,7 @@ func setupWhoami(c *caddy.Controller) error {
~~~
We use the `*caddy.Controller` to receive tokens from the Corefile and act upon them. Here we only
check if there is nothing specified after the token `whoami`. If you need to do more there are
`c.Val()`, `c.Args()` and [friends](https://godoc.org/github.com/mholt/caddy/caddyfile#Dispenser).
`c.Val()`, `c.Args()` and [friends](https://godoc.org/github.com/caddyserver/caddy/caddyfile#Dispenser).
The full `setup.go` for *whoami* is [here](https://github.com/miekg/coredns/blob/master/plugin/whoami/setup.go).
@@ -118,12 +109,11 @@ Next we setup the reply message:
~~~ go
a := &dns.Msg{}
a.SetReply(r)
a.Compress = true
a.Authoritative = true
~~~
We create a new message and copy the relevant bits from the incoming reply to the one we're planning
to return. We fiddle with some message bits and turn on message compression.
to return. We fiddle with some message bits and set it to authoritative.
Then we are going to inspect the incoming message via the `state` helper struct to see what we
should return.
@@ -167,18 +157,14 @@ In the last bit of this method we create the full message and send it:
~~~go
a.Extra = []dns.RR{rr, srv}
state.SizeAndDo(a)
w.WriteMsg(a)
return 0, nil
~~~
First we add the two created resource records (`rr` and `srv`) to the additional section of the
answer message: `a.Extra`.
Then we call `state.SizeOnDo(a)` which will set the correct EDNS0 record on the message and handle
setting the truncation bit). Then,
finally, we call the `WriteMsg` method on `w` which writes the message back to the client.
Then, finally, we call the `WriteMsg` method on `w` which writes the message back to the client.
We indicate a successful (even though it might have failed - we didn't check the return value of
`WriteMsg`) write by returning 0 and `nil`.
@@ -194,13 +180,13 @@ whoami:whoami
The initial number is used for sorting the plugin (more on that below), then the name of the
plugin as used in the registration and then the package inside the CoreDNS plugin directory.
Each plugin has a place in the list of all other ones. For instance the caching
or the metrics plugin needs to come early, so that it can "see" the query and
response and do cachy or metricsy things with it. A *whoami* plugin is not that special and can
be placed relatively late in the list (hence the high number).
Each plugin has a place in the list of all other ones. For instance the caching or the metrics
plugin needs to come early, so that it can "see" the query and response and do cachy or metricsy
things with it. A *whoami* plugin is not that special and can be placed relatively late in the list
(hence the high number).
Now do a `make` (or `go generate && go build`) to get a `coredns` binary that can be used with our shiny new plugin.
This binary should include `dns.whoami` when called with `-plugins`.
Now do a `make` (or `go generate && go build`) to get a `coredns` binary that can be used with our
shiny new plugin. This binary should include `dns.whoami` when called with `-plugins`.
# 5. Using it
@@ -214,15 +200,18 @@ Write a Corefile:
In words this says: be authoritative for the root `.` and below, meaning all possible queries will
hit this stanza. And for each request call *whoami*.
Start CoreDNS with: `coredns -conf Corefile -dns.port 1053`. Couple of notes here. CoreDNS
will look for a Corefile in the current directory so the `-conf Corefile` is only given here for
completeness. The `-dns.port` will start CoreDNS on port 1053, so we don't need to run as root.
Start CoreDNS with: `coredns -conf Corefile -dns.port 1053`. Couple of notes here. CoreDNS will look
for a Corefile in the current directory so the `-conf Corefile` is only given here for completeness.
The `-dns.port` will start CoreDNS on port 1053, so we don't need to run as root.
CoreDNS will output the following:
~~~ txt
.:1053
2017/02/19 17:44:31 [INFO] CoreDNS-005
CoreDNS-005
______ ____ _ _______
/ ____/___ ________ / __ \/ | / / ___/ ~ CoreDNS-1.6.4
/ / / __ \/ ___/ _ \/ / / / |/ /\__ \ ~ linux/amd64, go1.13.1, b139ba3
/ /___/ /_/ / / / __/ /_/ / /| /___/ /
\____/\____/_/ \___/_____/_/ |_//____/
~~~
The `.:1053` indicates it has parsed our Corefile and is listening on port 1053 for queries for
the root `.` zone and below.
@@ -245,8 +234,11 @@ _tcp.example.org. 0 IN SRV 0 0 33435 .
~~~
Yes, it correctly sees we've used TCP this time (and of course a different port).
There are already a lot of different plugin in CoreDNS. New, exciting ones, are always welcome. So if you
have an idea open an issue on the tracker.
There are already a lot of different plugin in CoreDNS. New, exciting ones, are always welcome. So
if you have an idea open an issue on the tracker.
This is an updated version of the [previous
blog](/2016/12/19/writing-plugins-for-coredns/) about writing CoreDNS plugin.
This is an updated version of the [previous blog](/2016/12/19/writing-plugins-for-coredns/) about
writing CoreDNS plugin.
Also see the [example plugin](https://github.com/coredns/example) for a simpler, plugin that just
serves as an example.
+7 -5
View File
@@ -7,18 +7,20 @@ author = "miek"
+++
The `plugin.md` in the CoreDNS source tree has some pointers on what a plugin for CoreDNS
should have as minimum requirements. It basically boils down to: documentation, tests and working.
should have as minimum requirements. It basically boils down to: "it should add something unique and
useful to CoreDNS". Further more documentation, tests and functionality should all be excellent.
It is easier to list when a plugin can be *included* in CoreDNS than to say it should stay
external, so we will do the first:
external, so we will do that:
* First, the plugin should be useful for *other* people. "Useful" is a subjective term, but the
plugin needs to fill a niche that appeals to more than one person.
* It should be sufficiently different from other plugin to warrant inclusion.
* It should be sufficiently different from other plugins to warrant inclusion.
* Current internet standards need be supported: IPv4 and IPv6, so A and AAAA records should be
handled (if your plugin is in the business of dealing with address records that is).
* It must have tests.
* It must have a README.md for documentation.
* Care must be taken to make it efficient in both memory and CPU.
Plugin for CoreDNS can live out-of-tree, `plugin.cfg` defaults to CoreDNS' repo but other
repos work just as well, so it is pretty easy to use your plugin even though it is out-of-tree.
Plugins for CoreDNS can easily live out-of-tree, `plugin.cfg` defaults to CoreDNS' repo but other
repos work just as well.