httpcaddyfile: Make global options pluggable (#3265)

* httpcaddyfile: Make global options pluggable

* httpcaddyfile: Add a global options adapt test

* httpcaddyfile: Wrap err

Co-Authored-By: Dave Henderson <dhenderson@gmail.com>

* httpcaddyfile: Revert wrap err

Co-authored-by: Dave Henderson <dhenderson@gmail.com>
This commit is contained in:
Francis Lavoie 2020-05-11 17:00:35 -04:00 committed by GitHub
parent 4c55d26f11
commit dc9f4f13fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 41 deletions

View file

@ -120,6 +120,17 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
}) })
} }
// RegisterGlobalOption registers a unique global option opt with
// an associated unmarshaling (setup) function. When the global
// option opt is encountered in a Caddyfile, setupFunc will be
// called to unmarshal its tokens.
func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
if _, ok := registeredGlobalOptions[opt]; ok {
panic("global option " + opt + " already registered")
}
registeredGlobalOptions[opt] = setupFunc
}
// Helper is a type which helps setup a value from // Helper is a type which helps setup a value from
// Caddyfile tokens. // Caddyfile tokens.
type Helper struct { type Helper struct {
@ -454,6 +465,13 @@ type (
// for you. These are passed to a call to // for you. These are passed to a call to
// RegisterHandlerDirective. // RegisterHandlerDirective.
UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error) UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
// UnmarshalGlobalFunc is a function which can unmarshal Caddyfile
// tokens into a global option config value using a Helper type.
// These are passed in a call to RegisterGlobalOption.
UnmarshalGlobalFunc func(d *caddyfile.Dispenser) (interface{}, error)
) )
var registeredDirectives = make(map[string]UnmarshalFunc) var registeredDirectives = make(map[string]UnmarshalFunc)
var registeredGlobalOptions = make(map[string]UnmarshalGlobalFunc)

View file

@ -284,39 +284,18 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
var val interface{} var val interface{}
var err error var err error
disp := caddyfile.NewDispenser(segment) disp := caddyfile.NewDispenser(segment)
switch dir {
case "debug": dirFunc, ok := registeredGlobalOptions[dir]
val = true if !ok {
case "http_port": tkn := segment[0]
val, err = parseOptHTTPPort(disp) return nil, fmt.Errorf("%s:%d: unrecognized global option: %s", tkn.File, tkn.Line, dir)
case "https_port":
val, err = parseOptHTTPSPort(disp)
case "default_sni":
val, err = parseOptSingleString(disp)
case "order":
val, err = parseOptOrder(disp)
case "experimental_http3":
val, err = parseOptExperimentalHTTP3(disp)
case "storage":
val, err = parseOptStorage(disp)
case "acme_ca", "acme_dns", "acme_ca_root":
val, err = parseOptSingleString(disp)
case "email":
val, err = parseOptSingleString(disp)
case "admin":
val, err = parseOptAdmin(disp)
case "on_demand_tls":
val, err = parseOptOnDemand(disp)
case "local_certs":
val = true
case "key_type":
val, err = parseOptSingleString(disp)
default:
return nil, fmt.Errorf("unrecognized parameter name: %s", dir)
} }
val, err = dirFunc(disp)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %v", dir, err) return nil, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
} }
options[dir] = val options[dir] = val
} }

View file

@ -23,7 +23,29 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
) )
func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) { func init() {
RegisterGlobalOption("debug", parseOptTrue)
RegisterGlobalOption("http_port", parseOptHTTPPort)
RegisterGlobalOption("https_port", parseOptHTTPSPort)
RegisterGlobalOption("default_sni", parseOptSingleString)
RegisterGlobalOption("order", parseOptOrder)
RegisterGlobalOption("experimental_http3", parseOptTrue)
RegisterGlobalOption("storage", parseOptStorage)
RegisterGlobalOption("acme_ca", parseOptSingleString)
RegisterGlobalOption("acme_dns", parseOptSingleString)
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
RegisterGlobalOption("email", parseOptSingleString)
RegisterGlobalOption("admin", parseOptAdmin)
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString)
}
func parseOptTrue(d *caddyfile.Dispenser) (interface{}, error) {
return true, nil
}
func parseOptHTTPPort(d *caddyfile.Dispenser) (interface{}, error) {
var httpPort int var httpPort int
for d.Next() { for d.Next() {
var httpPortStr string var httpPortStr string
@ -39,7 +61,7 @@ func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) {
return httpPort, nil return httpPort, nil
} }
func parseOptHTTPSPort(d *caddyfile.Dispenser) (int, error) { func parseOptHTTPSPort(d *caddyfile.Dispenser) (interface{}, error) {
var httpsPort int var httpsPort int
for d.Next() { for d.Next() {
var httpsPortStr string var httpsPortStr string
@ -55,11 +77,7 @@ func parseOptHTTPSPort(d *caddyfile.Dispenser) (int, error) {
return httpsPort, nil return httpsPort, nil
} }
func parseOptExperimentalHTTP3(d *caddyfile.Dispenser) (bool, error) { func parseOptOrder(d *caddyfile.Dispenser) (interface{}, error) {
return true, nil
}
func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
newOrder := directiveOrder newOrder := directiveOrder
for d.Next() { for d.Next() {
@ -135,7 +153,7 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
return newOrder, nil return newOrder, nil
} }
func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) { func parseOptStorage(d *caddyfile.Dispenser) (interface{}, error) {
if !d.Next() { // consume option name if !d.Next() { // consume option name
return nil, d.ArgErr() return nil, d.ArgErr()
} }
@ -162,7 +180,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
return storage, nil return storage, nil
} }
func parseOptSingleString(d *caddyfile.Dispenser) (string, error) { func parseOptSingleString(d *caddyfile.Dispenser) (interface{}, error) {
d.Next() // consume parameter name d.Next() // consume parameter name
if !d.Next() { if !d.Next() {
return "", d.ArgErr() return "", d.ArgErr()
@ -174,7 +192,7 @@ func parseOptSingleString(d *caddyfile.Dispenser) (string, error) {
return val, nil return val, nil
} }
func parseOptAdmin(d *caddyfile.Dispenser) (string, error) { func parseOptAdmin(d *caddyfile.Dispenser) (interface{}, error) {
if d.Next() { if d.Next() {
var listenAddress string var listenAddress string
if !d.AllArgs(&listenAddress) { if !d.AllArgs(&listenAddress) {
@ -188,7 +206,7 @@ func parseOptAdmin(d *caddyfile.Dispenser) (string, error) {
return "", nil return "", nil
} }
func parseOptOnDemand(d *caddyfile.Dispenser) (*caddytls.OnDemandConfig, error) { func parseOptOnDemand(d *caddyfile.Dispenser) (interface{}, error) {
var ond *caddytls.OnDemandConfig var ond *caddytls.OnDemandConfig
for d.Next() { for d.Next() {
if d.NextArg() { if d.NextArg() {

View file

@ -415,3 +415,77 @@ func TestNotBlockMerging(t *testing.T) {
} }
}`) }`)
} }
func TestGlobalOptions(t *testing.T) {
caddytest.AssertAdapt(t, `
{
debug
http_port 8080
https_port 8443
default_sni localhost
order root first
storage file_system {
root /data
}
acme_ca https://example.com
acme_ca_root /path/to/ca.crt
email test@example.com
admin off
on_demand_tls {
ask https://example.com
interval 30s
burst 20
}
local_certs
key_type ed25519
}
:80
`, "caddyfile", `{
"admin": {
"disabled": true
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"storage": {
"module": "file_system",
"root": "/data"
},
"apps": {
"http": {
"http_port": 8080,
"https_port": 8443,
"servers": {
"srv0": {
"listen": [
":80"
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuer": {
"module": "internal"
}
}
],
"on_demand": {
"rate_limit": {
"interval": 30000000000,
"burst": 20
},
"ask": "https://example.com"
}
}
}
}
}`)
}