mirror of
https://github.com/caddyserver/caddy.git
synced 2025-12-08 06:09:53 +00:00
Merge 87ec2b70f2 into 31960dc998
This commit is contained in:
commit
114a9d0cef
14 changed files with 981 additions and 313 deletions
182
caddyconfig/configbuilder/configbuilder.go
Normal file
182
caddyconfig/configbuilder/configbuilder.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy 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 configbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
)
|
||||
|
||||
// Builder provides methods for safely building a Caddy configuration
|
||||
// by accumulating apps, admin config, logging, storage, etc.
|
||||
// It prevents common errors like duplicate app names.
|
||||
type Builder struct {
|
||||
config *caddy.Config
|
||||
warnings *[]caddyconfig.Warning
|
||||
// apps stores structured app configs before marshaling
|
||||
// Multiple blocks can contribute to the same app
|
||||
apps map[string]any
|
||||
}
|
||||
|
||||
// New creates a new config builder with an empty configuration.
|
||||
func New() *Builder {
|
||||
warnings := []caddyconfig.Warning{}
|
||||
return &Builder{
|
||||
config: &caddy.Config{
|
||||
AppsRaw: make(caddy.ModuleMap),
|
||||
},
|
||||
warnings: &warnings,
|
||||
apps: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
// Config returns the built configuration.
|
||||
// This automatically finalizes any structured apps that haven't been marshaled yet.
|
||||
func (b *Builder) Config() *caddy.Config {
|
||||
b.finalize()
|
||||
return b.config
|
||||
}
|
||||
|
||||
// Warnings returns the accumulated warnings.
|
||||
func (b *Builder) Warnings() []caddyconfig.Warning {
|
||||
return *b.warnings
|
||||
}
|
||||
|
||||
// AddWarning adds a warning to the builder.
|
||||
func (b *Builder) AddWarning(message string) {
|
||||
*b.warnings = append(*b.warnings, caddyconfig.Warning{
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
// GetApp retrieves a structured app config by name.
|
||||
// Returns a pointer to the app and true if found, nil and false otherwise.
|
||||
// The returned value can be type-asserted to the specific app type.
|
||||
// Blocks should use this to get an existing app before modifying it.
|
||||
func (b *Builder) GetApp(name string) (any, bool) {
|
||||
app, ok := b.apps[name]
|
||||
return app, ok
|
||||
}
|
||||
|
||||
// GetTypedApp retrieves a structured app config by name with the correct type.
|
||||
// Returns a pointer to the app and true if found, nil and false otherwise.
|
||||
// This is a type-safe alternative to GetApp that uses generics.
|
||||
// Example: httpApp, ok := builder.GetTypedApp[caddyhttp.App]("http")
|
||||
func GetTypedApp[T any](b *Builder, name string) (*T, bool) {
|
||||
app, ok := b.apps[name]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
typedApp, ok := app.(*T)
|
||||
return typedApp, ok
|
||||
}
|
||||
|
||||
// CreateApp stores a new structured app config by name.
|
||||
// Returns an error if an app with this name already exists.
|
||||
// Blocks that want to modify an existing app should use GetApp() first.
|
||||
func (b *Builder) CreateApp(name string, app any) error {
|
||||
if _, exists := b.apps[name]; exists {
|
||||
return fmt.Errorf("app '%s' already exists", name)
|
||||
}
|
||||
b.apps[name] = app
|
||||
return nil
|
||||
}
|
||||
|
||||
// finalize marshals all structured apps to JSON and adds them to the config.
|
||||
// This is called automatically by Config().
|
||||
func (b *Builder) finalize() {
|
||||
for name, app := range b.apps {
|
||||
b.addApp(name, app)
|
||||
}
|
||||
// Clear apps after finalizing so we don't re-add them if Config() is called again
|
||||
b.apps = make(map[string]any)
|
||||
}
|
||||
|
||||
// addApp is an internal method to add an app to the configuration by marshaling it to JSON.
|
||||
// If an app with the same name already exists, a warning is added and the duplicate is ignored.
|
||||
func (b *Builder) addApp(name string, val any) {
|
||||
if b.config.AppsRaw == nil {
|
||||
b.config.AppsRaw = make(caddy.ModuleMap)
|
||||
}
|
||||
|
||||
if _, exists := b.config.AppsRaw[name]; exists {
|
||||
b.AddWarning(fmt.Sprintf("app '%s' already exists in configuration, ignoring duplicate declaration", name))
|
||||
return
|
||||
}
|
||||
|
||||
b.config.AppsRaw[name] = caddyconfig.JSON(val, b.warnings)
|
||||
}
|
||||
|
||||
// SetAdmin sets the admin configuration.
|
||||
// If admin config already exists, it will be replaced.
|
||||
func (b *Builder) SetAdmin(admin *caddy.AdminConfig) {
|
||||
b.config.Admin = admin
|
||||
}
|
||||
|
||||
// GetAdmin returns the admin configuration, creating it if it doesn't exist.
|
||||
func (b *Builder) GetAdmin() *caddy.AdminConfig {
|
||||
if b.config.Admin == nil {
|
||||
b.config.Admin = new(caddy.AdminConfig)
|
||||
}
|
||||
return b.config.Admin
|
||||
}
|
||||
|
||||
// SetStorage sets the storage configuration from a StorageConverter.
|
||||
// If storage config already exists, it will be replaced.
|
||||
func (b *Builder) SetStorage(storage caddy.StorageConverter) {
|
||||
b.config.StorageRaw = caddyconfig.JSONModuleObject(
|
||||
storage,
|
||||
"module",
|
||||
storage.(caddy.Module).CaddyModule().ID.Name(),
|
||||
b.warnings,
|
||||
)
|
||||
}
|
||||
|
||||
// SetDefaultLogger sets the default logger configuration.
|
||||
func (b *Builder) SetDefaultLogger(log *caddy.CustomLog) {
|
||||
if b.config.Logging == nil {
|
||||
b.config.Logging = &caddy.Logging{
|
||||
Logs: make(map[string]*caddy.CustomLog),
|
||||
}
|
||||
}
|
||||
if b.config.Logging.Logs == nil {
|
||||
b.config.Logging.Logs = make(map[string]*caddy.CustomLog)
|
||||
}
|
||||
b.config.Logging.Logs[caddy.DefaultLoggerName] = log
|
||||
}
|
||||
|
||||
// SetLogger sets a named logger configuration.
|
||||
func (b *Builder) SetLogger(name string, log *caddy.CustomLog) {
|
||||
if b.config.Logging == nil {
|
||||
b.config.Logging = &caddy.Logging{
|
||||
Logs: make(map[string]*caddy.CustomLog),
|
||||
}
|
||||
}
|
||||
if b.config.Logging.Logs == nil {
|
||||
b.config.Logging.Logs = make(map[string]*caddy.CustomLog)
|
||||
}
|
||||
b.config.Logging.Logs[name] = log
|
||||
}
|
||||
|
||||
// AddRawApp adds a raw JSON app to the configuration.
|
||||
// This is useful for adding apps that come from global options as pre-marshaled JSON.
|
||||
func (b *Builder) AddRawApp(name string, rawJSON []byte) {
|
||||
if b.config.AppsRaw == nil {
|
||||
b.config.AppsRaw = make(caddy.ModuleMap)
|
||||
}
|
||||
b.config.AppsRaw[name] = rawJSON
|
||||
}
|
||||
|
|
@ -77,10 +77,10 @@ import (
|
|||
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
||||
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
||||
// (Doing this is essentially a map-reduce technique.)
|
||||
func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
|
||||
func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []ServerBlock,
|
||||
options map[string]any,
|
||||
) (map[string]map[string][]serverBlock, error) {
|
||||
addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}
|
||||
) (map[string]map[string][]ServerBlock, error) {
|
||||
addrToProtocolToServerBlocks := map[string]map[string][]ServerBlock{}
|
||||
|
||||
type keyWithParsedKey struct {
|
||||
key caddyfile.Token
|
||||
|
|
@ -94,7 +94,7 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []
|
|||
// key of a server block as its own, but without having to repeat its
|
||||
// contents in cases where multiple keys really can be served together
|
||||
addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
|
||||
for j, key := range sblock.block.Keys {
|
||||
for j, key := range sblock.Block.Keys {
|
||||
parsedKey, err := ParseAddress(key.Text)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing key: %v", err)
|
||||
|
|
@ -154,7 +154,7 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []
|
|||
|
||||
protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
|
||||
if !ok {
|
||||
protocolToServerBlocks = map[string][]serverBlock{}
|
||||
protocolToServerBlocks = map[string][]ServerBlock{}
|
||||
addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
|
||||
}
|
||||
|
||||
|
|
@ -169,13 +169,13 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []
|
|||
parsedKeys[k] = keyWithParsedKey.parsedKey
|
||||
}
|
||||
|
||||
protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
|
||||
block: caddyfile.ServerBlock{
|
||||
protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], ServerBlock{
|
||||
Block: caddyfile.ServerBlock{
|
||||
Keys: keys,
|
||||
Segments: sblock.block.Segments,
|
||||
Segments: sblock.Block.Segments,
|
||||
},
|
||||
pile: sblock.pile,
|
||||
parsedKeys: parsedKeys,
|
||||
Pile: sblock.Pile,
|
||||
ParsedKeys: parsedKeys,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []
|
|||
// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
||||
// association from multiple addresses to multiple server blocks; i.e. each element of
|
||||
// the returned slice) becomes a server definition in the output JSON.
|
||||
func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
|
||||
func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]ServerBlock) []sbAddrAssociation {
|
||||
sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))
|
||||
|
||||
addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
|
||||
|
|
@ -267,7 +267,7 @@ func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[s
|
|||
|
||||
// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
|
||||
// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
|
||||
func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
|
||||
func (st *ServerType) listenersForServerBlockAddress(sblock ServerBlock, addr Address,
|
||||
options map[string]any,
|
||||
) (map[string]map[string]struct{}, error) {
|
||||
switch addr.Scheme {
|
||||
|
|
@ -307,8 +307,8 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
|
|||
}
|
||||
|
||||
// the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
|
||||
lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
|
||||
for _, cfgVal := range sblock.pile["bind"] {
|
||||
lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.Pile["bind"]))
|
||||
for _, cfgVal := range sblock.Pile["bind"] {
|
||||
if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
|
||||
lnCfgVals = append(lnCfgVals, val)
|
||||
}
|
||||
|
|
|
|||
127
caddyconfig/httpcaddyfile/blocktypes/blocktypes.go
Normal file
127
caddyconfig/httpcaddyfile/blocktypes/blocktypes.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy 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 blocktypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/configbuilder"
|
||||
)
|
||||
|
||||
// BlockTypeSetupFunc is a function that processes server blocks of a specific type
|
||||
// and builds the configuration using the provided builder. It receives a config builder,
|
||||
// the server blocks to process, and any global options.
|
||||
type BlockTypeSetupFunc func(builder *configbuilder.Builder, blocks []caddyfile.ServerBlock, options map[string]any) ([]caddyconfig.Warning, error)
|
||||
|
||||
// BlockTypeInfo contains metadata about a registered block type.
|
||||
type BlockTypeInfo struct {
|
||||
SetupFunc BlockTypeSetupFunc
|
||||
Parent string // Parent block type (e.g., "http" for "http.server")
|
||||
}
|
||||
|
||||
var (
|
||||
registeredBlockTypes = make(map[string]*BlockTypeInfo)
|
||||
blockTypesMu sync.RWMutex
|
||||
)
|
||||
|
||||
// RegisterBlockType registers a top-level block type handler with the given name.
|
||||
// This allows the block type to be used in xcaddyfile configurations with [name] syntax.
|
||||
// For example, RegisterBlockType("http", setupFunc) allows [http] blocks.
|
||||
//
|
||||
// Block type handlers are responsible for parsing their specific block types
|
||||
// and mutating the provided caddy.Config accordingly.
|
||||
func RegisterBlockType(name string, setupFunc BlockTypeSetupFunc) {
|
||||
blockTypesMu.Lock()
|
||||
defer blockTypesMu.Unlock()
|
||||
|
||||
if _, exists := registeredBlockTypes[name]; exists {
|
||||
panic(fmt.Sprintf("block type %s already registered", name))
|
||||
}
|
||||
|
||||
registeredBlockTypes[name] = &BlockTypeInfo{
|
||||
SetupFunc: setupFunc,
|
||||
Parent: "", // No parent for top-level block types
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterChildBlockType registers a hierarchical block type under a parent.
|
||||
// For example, RegisterChildBlockType("http.server", "http", setupFunc) registers
|
||||
// [http.server] as a child of [http].
|
||||
//
|
||||
// Child blocks are processed after their parent and can access context set by the parent.
|
||||
func RegisterChildBlockType(name, parent string, setupFunc BlockTypeSetupFunc) {
|
||||
blockTypesMu.Lock()
|
||||
defer blockTypesMu.Unlock()
|
||||
|
||||
if _, exists := registeredBlockTypes[name]; exists {
|
||||
panic(fmt.Sprintf("block type %s already registered", name))
|
||||
}
|
||||
|
||||
registeredBlockTypes[name] = &BlockTypeInfo{
|
||||
SetupFunc: setupFunc,
|
||||
Parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlockType retrieves a registered block type handler by name.
|
||||
// Returns the handler function and true if found, nil and false otherwise.
|
||||
func GetBlockType(name string) (BlockTypeSetupFunc, bool) {
|
||||
blockTypesMu.RLock()
|
||||
defer blockTypesMu.RUnlock()
|
||||
|
||||
info, exists := registeredBlockTypes[name]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
return info.SetupFunc, true
|
||||
}
|
||||
|
||||
// GetBlockTypeInfo retrieves full block type info including parent relationship.
|
||||
func GetBlockTypeInfo(name string) (*BlockTypeInfo, bool) {
|
||||
blockTypesMu.RLock()
|
||||
defer blockTypesMu.RUnlock()
|
||||
|
||||
info, exists := registeredBlockTypes[name]
|
||||
return info, exists
|
||||
}
|
||||
|
||||
// GetChildBlockTypes returns all child block types for a given parent.
|
||||
func GetChildBlockTypes(parent string) []string {
|
||||
blockTypesMu.RLock()
|
||||
defer blockTypesMu.RUnlock()
|
||||
|
||||
var children []string
|
||||
for name, info := range registeredBlockTypes {
|
||||
if info.Parent == parent {
|
||||
children = append(children, name)
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
// RegisteredBlockTypes returns a list of all registered block type names.
|
||||
func RegisteredBlockTypes() []string {
|
||||
blockTypesMu.RLock()
|
||||
defer blockTypesMu.RUnlock()
|
||||
|
||||
names := make([]string, 0, len(registeredBlockTypes))
|
||||
for name := range registeredBlockTypes {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy 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 globalblock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/configbuilder"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes"
|
||||
)
|
||||
|
||||
func init() {
|
||||
blocktypes.RegisterBlockType("global", setup)
|
||||
}
|
||||
|
||||
// setup processes global configuration blocks which store global options
|
||||
// (http_port, https_port, grace_period, etc.) in options for other block parsers to use.
|
||||
func setup(builder *configbuilder.Builder, blocks []caddyfile.ServerBlock, options map[string]any) ([]caddyconfig.Warning, error) {
|
||||
var warnings []caddyconfig.Warning
|
||||
|
||||
// Process each global block
|
||||
for _, block := range blocks {
|
||||
// Global blocks should not have keys
|
||||
if len(block.Keys) > 0 {
|
||||
return warnings, fmt.Errorf("[global] blocks should not have keys")
|
||||
}
|
||||
|
||||
// Use httpcaddyfile's EvaluateGlobalOptions for all global options
|
||||
if err := httpcaddyfile.EvaluateGlobalOptions(block.Segments, options); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy 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 httpserverblock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/configbuilder"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
blocktypes.RegisterChildBlockType("http.server", "global", setup)
|
||||
}
|
||||
|
||||
// extractServerBlocks converts raw caddyfile blocks to httpcaddyfile serverBlock format
|
||||
func extractServerBlocks(inputBlocks []caddyfile.ServerBlock, warnings []caddyconfig.Warning) ([]httpcaddyfile.ServerBlock, []caddyconfig.Warning, error) {
|
||||
serverBlocks := make([]httpcaddyfile.ServerBlock, 0, len(inputBlocks))
|
||||
for _, sblock := range inputBlocks {
|
||||
for j, k := range sblock.Keys {
|
||||
if j == 0 && strings.HasPrefix(k.Text, "@") {
|
||||
return nil, warnings, fmt.Errorf("%s:%d: cannot define a matcher outside of a site block: '%s'", k.File, k.Line, k.Text)
|
||||
}
|
||||
if httpcaddyfile.DirectiveIsRegistered(k.Text) {
|
||||
return nil, warnings, fmt.Errorf("%s:%d: parsed '%s' as a site address, but it is a known directive; directives must appear in a site block", k.File, k.Line, k.Text)
|
||||
}
|
||||
}
|
||||
serverBlocks = append(serverBlocks, httpcaddyfile.ServerBlock{
|
||||
Block: sblock,
|
||||
Pile: make(map[string][]httpcaddyfile.ConfigValue),
|
||||
})
|
||||
}
|
||||
return serverBlocks, warnings, nil
|
||||
}
|
||||
|
||||
// setup processes [http.server] blocks using the httpcaddyfile adapter.
|
||||
// This leverages all existing HTTP configuration logic.
|
||||
// The [global] block should have been processed first to set up global options.
|
||||
func setup(builder *configbuilder.Builder, blocks []caddyfile.ServerBlock, options map[string]any) ([]caddyconfig.Warning, error) {
|
||||
var warnings []caddyconfig.Warning
|
||||
|
||||
// Extract server blocks with validation
|
||||
serverBlocks, warnings, err := extractServerBlocks(blocks, warnings)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
// Use httpcaddyfile.BuildServersAndPairings to build servers and pairings
|
||||
servers, pairings, metrics, warnings, err := httpcaddyfile.BuildServersAndPairings(serverBlocks, options, warnings)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
// Collect server-specific custom logs for xcaddyfiletype to process later
|
||||
// Use a unique key name to avoid conflicts with other options
|
||||
serverLogs := httpcaddyfile.CollectServerLogs(pairings)
|
||||
if len(serverLogs) > 0 {
|
||||
options["__xcaddyfile_server_logs__"] = serverLogs
|
||||
}
|
||||
|
||||
// Construct the HTTP app from the servers
|
||||
// Use options from [global] block if they were set
|
||||
httpApp := &caddyhttp.App{
|
||||
HTTPPort: httpcaddyfile.TryInt(options["http_port"], &warnings),
|
||||
HTTPSPort: httpcaddyfile.TryInt(options["https_port"], &warnings),
|
||||
GracePeriod: httpcaddyfile.TryDuration(options["grace_period"], &warnings),
|
||||
ShutdownDelay: httpcaddyfile.TryDuration(options["shutdown_delay"], &warnings),
|
||||
Metrics: metrics,
|
||||
Servers: servers,
|
||||
}
|
||||
|
||||
// Create the HTTP app (should be the first time, since [global] doesn't create it)
|
||||
if err := builder.CreateApp("http", httpApp); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
// Build TLS app using the pairings
|
||||
tlsApp, tlsWarnings, err := httpcaddyfile.BuildTLSApp(pairings, options, warnings)
|
||||
if err != nil {
|
||||
return append(warnings, tlsWarnings...), err
|
||||
}
|
||||
warnings = append(warnings, tlsWarnings...)
|
||||
// Only add TLS app if it's not empty (has certificates or automation policies)
|
||||
if tlsApp != nil && (!reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)})) {
|
||||
if err := builder.CreateApp("tls", tlsApp); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
}
|
||||
|
||||
// Build PKI app using the pairings
|
||||
pkiApp, pkiWarnings, err := httpcaddyfile.BuildPKIApp(pairings, options, warnings)
|
||||
if err != nil {
|
||||
return append(warnings, pkiWarnings...), err
|
||||
}
|
||||
warnings = append(warnings, pkiWarnings...)
|
||||
// Only add PKI app if it's not empty (has CAs)
|
||||
if pkiApp != nil && (!reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)})) {
|
||||
if err := builder.CreateApp("pki", pkiApp); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package httpcaddyfile
|
||||
package httpcaddyfile_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/globalblock"
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/httpserverblock"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/logging"
|
||||
)
|
||||
|
||||
|
|
@ -79,7 +82,7 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
|||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
ServerType: httpcaddyfile.ServerType{},
|
||||
}
|
||||
|
||||
out, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
|
@ -220,7 +223,7 @@ func TestRedirDirectiveSyntax(t *testing.T) {
|
|||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
ServerType: httpcaddyfile.ServerType{},
|
||||
}
|
||||
|
||||
_, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
|
@ -283,7 +286,7 @@ func TestImportErrorLine(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
ServerType: httpcaddyfile.ServerType{},
|
||||
}
|
||||
|
||||
_, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
|
@ -356,7 +359,7 @@ func TestNestedImport(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
ServerType: httpcaddyfile.ServerType{},
|
||||
}
|
||||
|
||||
_, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
|
|
|||
|
|
@ -530,13 +530,13 @@ func sortRoutes(routes []ConfigValue) {
|
|||
})
|
||||
}
|
||||
|
||||
// serverBlock pairs a Caddyfile server block with
|
||||
// a "pile" of config values, keyed by class name,
|
||||
// as well as its parsed keys for convenience.
|
||||
type serverBlock struct {
|
||||
block caddyfile.ServerBlock
|
||||
pile map[string][]ConfigValue // config values obtained from directives
|
||||
parsedKeys []Address
|
||||
// ServerBlock pairs a Caddyfile server block with a "pile" of config values,
|
||||
// keyed by class name, as well as its parsed keys for convenience.
|
||||
// This is the unified type used by both httpcaddyfile and xcaddyfile.
|
||||
type ServerBlock struct {
|
||||
Block caddyfile.ServerBlock
|
||||
Pile map[string][]ConfigValue // config values obtained from directives
|
||||
ParsedKeys []Address
|
||||
}
|
||||
|
||||
// hostsFromKeys returns a list of all the non-empty hostnames found in
|
||||
|
|
@ -550,10 +550,10 @@ type serverBlock struct {
|
|||
// header of requests that come in for that key.
|
||||
//
|
||||
// The resulting slice is not sorted but will never have duplicates.
|
||||
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
||||
func (sb ServerBlock) hostsFromKeys(loggerMode bool) []string {
|
||||
// ensure each entry in our list is unique
|
||||
hostMap := make(map[string]struct{})
|
||||
for _, addr := range sb.parsedKeys {
|
||||
for _, addr := range sb.ParsedKeys {
|
||||
if addr.Host == "" {
|
||||
if !loggerMode {
|
||||
// server block contains a key like ":443", i.e. the host portion
|
||||
|
|
@ -582,10 +582,10 @@ func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
|||
return sblockHosts
|
||||
}
|
||||
|
||||
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
||||
func (sb ServerBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
||||
// ensure each entry in our list is unique
|
||||
hostMap := make(map[string]struct{})
|
||||
for _, addr := range sb.parsedKeys {
|
||||
for _, addr := range sb.ParsedKeys {
|
||||
if addr.Host == "" {
|
||||
continue
|
||||
}
|
||||
|
|
@ -605,16 +605,16 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
|||
|
||||
// hasHostCatchAllKey returns true if sb has a key that
|
||||
// omits a host portion, i.e. it "catches all" hosts.
|
||||
func (sb serverBlock) hasHostCatchAllKey() bool {
|
||||
return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
|
||||
func (sb ServerBlock) hasHostCatchAllKey() bool {
|
||||
return slices.ContainsFunc(sb.ParsedKeys, func(addr Address) bool {
|
||||
return addr.Host == ""
|
||||
})
|
||||
}
|
||||
|
||||
// isAllHTTP returns true if all sb keys explicitly specify
|
||||
// the http:// scheme
|
||||
func (sb serverBlock) isAllHTTP() bool {
|
||||
return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
|
||||
func (sb ServerBlock) isAllHTTP() bool {
|
||||
return !slices.ContainsFunc(sb.ParsedKeys, func(addr Address) bool {
|
||||
return addr.Scheme != "http"
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) {
|
|||
[]string{"example.com:2015"},
|
||||
},
|
||||
} {
|
||||
sb := serverBlock{parsedKeys: tc.keys}
|
||||
sb := ServerBlock{ParsedKeys: tc.keys}
|
||||
|
||||
// test in normal mode
|
||||
actual := sb.hostsFromKeys(false)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,12 @@
|
|||
package httpcaddyfile
|
||||
package httpcaddyfile_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/globalblock"
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/httpserverblock"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/logging"
|
||||
)
|
||||
|
||||
|
|
@ -47,7 +50,7 @@ func TestGlobalLogOptionSyntax(t *testing.T) {
|
|||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
ServerType: httpcaddyfile.ServerType{},
|
||||
}
|
||||
|
||||
out, _, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
|
|
|||
|
|
@ -171,6 +171,17 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
|||
return pki, nil
|
||||
}
|
||||
|
||||
// BuildPKIApp builds the PKI app from server block pairings and global options.
|
||||
// This is exported for use by xcaddyfile and other adapters.
|
||||
func BuildPKIApp(
|
||||
pairings ServerBlockPairings,
|
||||
options map[string]any,
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddypki.PKI, []caddyconfig.Warning, error) {
|
||||
st := ServerType{}
|
||||
return st.buildPKIApp(pairings, options, warnings)
|
||||
}
|
||||
|
||||
func (st ServerType) buildPKIApp(
|
||||
pairings []sbAddrAssociation,
|
||||
options map[string]any,
|
||||
|
|
@ -211,7 +222,7 @@ func (st ServerType) buildPKIApp(
|
|||
for _, sblock := range p.serverBlocks {
|
||||
// find all the CAs that were defined and add them to the app config
|
||||
// i.e. from any "acme_server" directives
|
||||
for _, caCfgValue := range sblock.pile["pki.ca"] {
|
||||
for _, caCfgValue := range sblock.Pile["pki.ca"] {
|
||||
ca := caCfgValue.Value.(*caddypki.CA)
|
||||
if skipInstallTrust {
|
||||
ca.InstallTrust = &falseBool
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
// serverOptions collects server config overrides parsed from Caddyfile global options
|
||||
type serverOptions struct {
|
||||
// ServerOptions collects server config overrides parsed from Caddyfile global options
|
||||
type ServerOptions struct {
|
||||
// If set, will only apply these options to servers that contain a
|
||||
// listener address that matches exactly. If empty, will apply to all
|
||||
// servers that were not already matched by another serverOptions.
|
||||
// servers that were not already matched by another ServerOptions.
|
||||
ListenerAddress string
|
||||
|
||||
// These will all map 1:1 to the caddyhttp.Server struct
|
||||
|
|
@ -62,7 +62,7 @@ type serverOptions struct {
|
|||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
d.Next() // consume option name
|
||||
|
||||
serverOpts := serverOptions{}
|
||||
serverOpts := ServerOptions{}
|
||||
if d.NextArg() {
|
||||
serverOpts.ListenerAddress = d.Val()
|
||||
if d.NextArg() {
|
||||
|
|
@ -322,7 +322,7 @@ func applyServerOptions(
|
|||
options map[string]any,
|
||||
_ *[]caddyconfig.Warning,
|
||||
) error {
|
||||
serverOpts, ok := options["servers"].([]serverOptions)
|
||||
serverOpts, ok := options["servers"].([]ServerOptions)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -344,7 +344,7 @@ func applyServerOptions(
|
|||
|
||||
for key, server := range servers {
|
||||
// find the options that apply to this server
|
||||
optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool {
|
||||
optsIndex := slices.IndexFunc(serverOpts, func(s ServerOptions) bool {
|
||||
return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
)
|
||||
|
||||
// BuildTLSApp builds the TLS app from server block pairings and global options.
|
||||
// This is exported for use by xcaddyfile and other adapters.
|
||||
func BuildTLSApp(
|
||||
pairings ServerBlockPairings,
|
||||
options map[string]any,
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddytls.TLS, []caddyconfig.Warning, error) {
|
||||
st := ServerType{}
|
||||
return st.buildTLSApp(pairings, options, warnings)
|
||||
}
|
||||
|
||||
func (st ServerType) buildTLSApp(
|
||||
pairings []sbAddrAssociation,
|
||||
options map[string]any,
|
||||
|
|
@ -57,14 +68,14 @@ func (st ServerType) buildTLSApp(
|
|||
if !slices.Contains(autoHTTPS, "off") {
|
||||
for _, pair := range pairings {
|
||||
for _, sb := range pair.serverBlocks {
|
||||
for _, addr := range sb.parsedKeys {
|
||||
for _, addr := range sb.ParsedKeys {
|
||||
if addr.Host != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// this server block has a hostless key, now
|
||||
// go through and add all the hosts to the set
|
||||
for _, otherAddr := range sb.parsedKeys {
|
||||
for _, otherAddr := range sb.ParsedKeys {
|
||||
if otherAddr.Original == addr.Original {
|
||||
continue
|
||||
}
|
||||
|
|
@ -104,7 +115,7 @@ func (st ServerType) buildTLSApp(
|
|||
continue
|
||||
}
|
||||
for _, sblock := range p.serverBlocks {
|
||||
for _, addr := range sblock.parsedKeys {
|
||||
for _, addr := range sblock.ParsedKeys {
|
||||
if strings.HasPrefix(addr.Host, "*.") {
|
||||
wildcardHosts = append(wildcardHosts, addr.Host[2:])
|
||||
}
|
||||
|
|
@ -147,28 +158,28 @@ func (st ServerType) buildTLSApp(
|
|||
}
|
||||
|
||||
// on-demand tls
|
||||
if _, ok := sblock.pile["tls.on_demand"]; ok {
|
||||
if _, ok := sblock.Pile["tls.on_demand"]; ok {
|
||||
ap.OnDemand = true
|
||||
}
|
||||
|
||||
// collect hosts that are forced to have certs automated for their specific name
|
||||
if _, ok := sblock.pile["tls.force_automate"]; ok {
|
||||
if _, ok := sblock.Pile["tls.force_automate"]; ok {
|
||||
for _, host := range sblockHosts {
|
||||
forcedAutomatedNames[host] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// reuse private keys tls
|
||||
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
|
||||
if _, ok := sblock.Pile["tls.reuse_private_keys"]; ok {
|
||||
ap.ReusePrivateKeys = true
|
||||
}
|
||||
|
||||
if keyTypeVals, ok := sblock.pile["tls.key_type"]; ok {
|
||||
if keyTypeVals, ok := sblock.Pile["tls.key_type"]; ok {
|
||||
ap.KeyType = keyTypeVals[0].Value.(string)
|
||||
}
|
||||
|
||||
// certificate issuers
|
||||
if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
|
||||
if issuerVals, ok := sblock.Pile["tls.cert_issuer"]; ok {
|
||||
var issuers []certmagic.Issuer
|
||||
for _, issuerVal := range issuerVals {
|
||||
issuers = append(issuers, issuerVal.Value.(certmagic.Issuer))
|
||||
|
|
@ -193,14 +204,14 @@ func (st ServerType) buildTLSApp(
|
|||
}
|
||||
|
||||
// certificate managers
|
||||
if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok {
|
||||
if certManagerVals, ok := sblock.Pile["tls.cert_manager"]; ok {
|
||||
for _, certManager := range certManagerVals {
|
||||
certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name()
|
||||
ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings))
|
||||
}
|
||||
}
|
||||
// custom bind host
|
||||
for _, cfgVal := range sblock.pile["bind"] {
|
||||
for _, cfgVal := range sblock.Pile["bind"] {
|
||||
for _, iss := range ap.Issuers {
|
||||
// if an issuer was already configured and it is NOT an ACME issuer,
|
||||
// skip, since we intend to adjust only ACME issuers; ensure we
|
||||
|
|
@ -313,7 +324,7 @@ func (st ServerType) buildTLSApp(
|
|||
}
|
||||
|
||||
// certificate loaders
|
||||
if clVals, ok := sblock.pile["tls.cert_loader"]; ok {
|
||||
if clVals, ok := sblock.Pile["tls.cert_loader"]; ok {
|
||||
for _, clVal := range clVals {
|
||||
certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package standard
|
|||
import (
|
||||
// standard Caddy modules
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/globalblock"
|
||||
_ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile/blocktypes/httpserverblock"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyevents"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyevents/eventsconfig"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyfs"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue