This commit is contained in:
a 2025-12-05 10:35:43 +05:30 committed by GitHub
commit 114a9d0cef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 981 additions and 313 deletions

View 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
}

View file

@ -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)
}

View 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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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"
})
}

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)
})

View file

@ -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))
}

View file

@ -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"