Documentation
¶
Index ¶
- Constants
- Variables
- func ExtractCaddyBinary(p *paths.Paths) (string, error)
- func GetCaddyBinaryPath(p *paths.Paths) (string, error)
- func HasTLSRules(ingresses []Ingress) bool
- func ParseCaddyError(caddyError string) error
- func SupportedDNSProviders() string
- type ACMEConfig
- type CaddyConfigGenerator
- type CaddyDaemon
- func (d *CaddyDaemon) AdminPort() int
- func (d *CaddyDaemon) AdminURL() string
- func (d *CaddyDaemon) DiscoverRunning() (int, bool)
- func (d *CaddyDaemon) GetPID() int
- func (d *CaddyDaemon) IsRunning() bool
- func (d *CaddyDaemon) ReloadConfig(config []byte) error
- func (d *CaddyDaemon) Start(ctx context.Context) (int, error)
- func (d *CaddyDaemon) Stop(ctx context.Context) error
- func (d *CaddyDaemon) StopOnShutdown() bool
- type CaddyLogForwarder
- type Config
- type CreateIngressRequest
- type DNSProvider
- type HostnamePattern
- type Ingress
- type IngressMatch
- type IngressRule
- type IngressTarget
- type InstanceResolver
- type Manager
- type ValidationError
Constants ¶
const CaddyVersion = "v2.10.2"
CaddyVersion is the version of Caddy embedded in this build.
const DefaultDNSPort = dns.DefaultPort
DefaultDNSPort is the default port for the internal DNS server.
Variables ¶
var ( // ErrNotFound is returned when an ingress is not found. ErrNotFound = errors.New("ingress not found") // ErrAlreadyExists is returned when trying to create an ingress that already exists. ErrAlreadyExists = errors.New("ingress already exists") // ErrInvalidRequest is returned when the request is invalid. ErrInvalidRequest = errors.New("invalid request") // ErrInstanceNotFound is returned when the target instance is not found. ErrInstanceNotFound = errors.New("target instance not found") // ErrInstanceNoNetwork is returned when the target instance has no network. ErrInstanceNoNetwork = errors.New("target instance has no network configured") // ErrHostnameInUse is returned when a hostname is already in use by another ingress. ErrHostnameInUse = errors.New("hostname already in use by another ingress") // ErrConfigValidationFailed is returned when Caddy config validation fails. // This indicates the config was rejected by Caddy's admin API. ErrConfigValidationFailed = errors.New("config validation failed") // ErrPortInUse is returned when the requested port is already in use by another process. ErrPortInUse = errors.New("port already in use") // ErrDomainNotAllowed is returned when a TLS ingress is requested for a domain not in the allowed list. ErrDomainNotAllowed = errors.New("domain not allowed for TLS") // ErrAmbiguousName is returned when a lookup matches multiple ingresses. ErrAmbiguousName = errors.New("ambiguous ingress identifier matches multiple ingresses") )
Common errors returned by the ingress package.
Functions ¶
func ExtractCaddyBinary ¶
ExtractCaddyBinary extracts the embedded Caddy binary to the data directory. Returns the path to the extracted binary. If the binary already exists but doesn't match the embedded version (e.g., after rebuilding with different modules), it will be re-extracted.
func GetCaddyBinaryPath ¶
GetCaddyBinaryPath returns path to extracted binary, extracting if needed.
func HasTLSRules ¶
HasTLSRules checks if any ingress has TLS enabled.
func ParseCaddyError ¶
ParseCaddyError parses a Caddy error response and returns a more specific error if possible.
func SupportedDNSProviders ¶
func SupportedDNSProviders() string
SupportedDNSProviders returns a comma-separated list of supported DNS provider names. Used in error messages to keep them in sync as new providers are added.
Types ¶
type ACMEConfig ¶
type ACMEConfig struct {
// Email is the ACME account email (required for TLS).
Email string
// DNSProvider is the DNS provider for ACME challenges.
DNSProvider DNSProvider
// CA is the ACME CA URL. Empty means Let's Encrypt production.
CA string
// DNS propagation settings (applies to all providers)
DNSPropagationTimeout string // Max time to wait for DNS propagation (e.g., "2m")
DNSResolvers string // Comma-separated DNS resolvers to use for checking propagation
// AllowedDomains is a comma-separated list of domain patterns allowed for TLS ingresses.
// Supports wildcards like "*.example.com" and exact matches like "api.example.com".
// If empty, no TLS domains are allowed.
AllowedDomains string
// Cloudflare API token (if DNSProvider=cloudflare).
CloudflareAPIToken string
}
ACMEConfig holds ACME/TLS configuration for Caddy.
func (*ACMEConfig) IsDomainAllowed ¶
func (c *ACMEConfig) IsDomainAllowed(hostname string) bool
IsDomainAllowed checks if a hostname is allowed for TLS based on the AllowedDomains config. Returns true if the hostname matches any of the allowed patterns.
Supported pattern types:
- Exact match: "api.example.com" matches only "api.example.com"
- Global wildcard: "*" matches any hostname (use with caution)
- Subdomain wildcard: "*.example.com" matches single-level subdomains only
Wildcard behavior for "*.example.com":
- Matches: "foo.example.com", "bar.example.com"
- Does NOT match: "example.com" (apex domain)
- Does NOT match: "foo.bar.example.com" (multi-level subdomain)
func (*ACMEConfig) IsTLSConfigured ¶
func (c *ACMEConfig) IsTLSConfigured() bool
IsTLSConfigured returns true if ACME/TLS is properly configured.
type CaddyConfigGenerator ¶
type CaddyConfigGenerator struct {
// contains filtered or unexported fields
}
CaddyConfigGenerator generates Caddy configuration from ingress resources.
func NewCaddyConfigGenerator ¶
func NewCaddyConfigGenerator(p *paths.Paths, listenAddress string, adminAddress string, adminPort int, acme ACMEConfig, dnsResolverPort int) *CaddyConfigGenerator
NewCaddyConfigGenerator creates a new Caddy config generator.
func (*CaddyConfigGenerator) GenerateConfig ¶
func (g *CaddyConfigGenerator) GenerateConfig(ctx context.Context, ingresses []Ingress) ([]byte, error)
GenerateConfig generates the Caddy JSON configuration.
func (*CaddyConfigGenerator) WriteConfig ¶
func (g *CaddyConfigGenerator) WriteConfig(ctx context.Context, ingresses []Ingress) error
WriteConfig writes the Caddy configuration to disk.
type CaddyDaemon ¶
type CaddyDaemon struct {
// contains filtered or unexported fields
}
CaddyDaemon manages the Caddy proxy daemon lifecycle. Caddy uses its admin API for configuration updates - no restart needed.
func NewCaddyDaemon ¶
func NewCaddyDaemon(p *paths.Paths, adminAddress string, adminPort int, stopOnShutdown bool) *CaddyDaemon
NewCaddyDaemon creates a new CaddyDaemon manager. If adminPort is 0, it will be resolved later from existing config or picked fresh.
func (*CaddyDaemon) AdminPort ¶
func (d *CaddyDaemon) AdminPort() int
AdminPort returns the admin port. If it was configured as 0 (random), this returns the actual port after it's been resolved.
func (*CaddyDaemon) AdminURL ¶
func (d *CaddyDaemon) AdminURL() string
AdminURL returns the admin API URL.
func (*CaddyDaemon) DiscoverRunning ¶
func (d *CaddyDaemon) DiscoverRunning() (int, bool)
DiscoverRunning checks if Caddy is already running and returns its PID.
func (*CaddyDaemon) GetPID ¶
func (d *CaddyDaemon) GetPID() int
GetPID returns the PID of the running Caddy process, or 0 if not running.
func (*CaddyDaemon) IsRunning ¶
func (d *CaddyDaemon) IsRunning() bool
IsRunning returns true if Caddy is currently running.
func (*CaddyDaemon) ReloadConfig ¶
func (d *CaddyDaemon) ReloadConfig(config []byte) error
ReloadConfig reloads Caddy configuration by posting to the admin API.
func (*CaddyDaemon) Start ¶
func (d *CaddyDaemon) Start(ctx context.Context) (int, error)
Start starts the Caddy daemon. If Caddy is already running (discovered via PID file or admin API), this is a no-op and returns the existing PID. Note: adminPort must be resolved (non-zero) before calling Start.
func (*CaddyDaemon) Stop ¶
func (d *CaddyDaemon) Stop(ctx context.Context) error
Stop gracefully stops the Caddy daemon.
func (*CaddyDaemon) StopOnShutdown ¶
func (d *CaddyDaemon) StopOnShutdown() bool
StopOnShutdown returns whether Caddy should be stopped when hypeman shuts down.
type CaddyLogForwarder ¶
type CaddyLogForwarder struct {
// contains filtered or unexported fields
}
CaddyLogForwarder tails Caddy's system log and forwards to OTEL.
func NewCaddyLogForwarder ¶
func NewCaddyLogForwarder(p *paths.Paths, logger *slog.Logger) *CaddyLogForwarder
NewCaddyLogForwarder creates a new log forwarder.
type Config ¶
type Config struct {
// ListenAddress is the address Caddy should listen on (default: 0.0.0.0).
ListenAddress string
// AdminAddress is the address for Caddy admin API (default: 127.0.0.1).
AdminAddress string
// AdminPort is the port for Caddy admin API (default: 2019).
AdminPort int
// DNSPort is the port for the internal DNS server used for dynamic upstream resolution.
// Default: 5353. Set to 0 to use a random available port.
DNSPort int
// StopOnShutdown determines whether to stop Caddy when hypeman shuts down (default: false).
// When false, Caddy continues running independently.
StopOnShutdown bool
// ACME configuration for TLS certificates
ACME ACMEConfig
}
Config holds configuration for the ingress manager.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns the default ingress configuration.
type CreateIngressRequest ¶
type CreateIngressRequest struct {
// Name is a human-readable name for the ingress.
Name string `json:"name"`
// Rules define the routing rules for this ingress.
Rules []IngressRule `json:"rules"`
}
CreateIngressRequest is the request body for creating a new ingress.
func (*CreateIngressRequest) Validate ¶
func (r *CreateIngressRequest) Validate() error
Validate validates the CreateIngressRequest.
type DNSProvider ¶
type DNSProvider string
DNSProvider represents supported DNS providers for ACME challenges.
const ( // DNSProviderNone indicates no DNS provider is configured. DNSProviderNone DNSProvider = "" // DNSProviderCloudflare uses Cloudflare for DNS challenges. DNSProviderCloudflare DNSProvider = "cloudflare" )
func ParseDNSProvider ¶
func ParseDNSProvider(s string) (DNSProvider, error)
ParseDNSProvider parses a string into a DNSProvider, returning an error for unknown values.
type HostnamePattern ¶
type HostnamePattern struct {
// Original is the original pattern string (e.g., "{instance}.example.com")
Original string
// Wildcard is the Caddy wildcard pattern (e.g., "*.example.com")
Wildcard string
// Captures is the list of capture names in order (e.g., ["instance"])
Captures []string
// CaddyLabels maps capture names to Caddy placeholder expressions
// e.g., {"instance": "{http.request.host.labels.2}"}
CaddyLabels map[string]string
}
HostnamePattern represents a parsed hostname pattern with captures.
func (*HostnamePattern) ResolveInstance ¶
func (p *HostnamePattern) ResolveInstance(targetInstance string) string
ResolveInstance resolves the target instance expression using the pattern's captures. For a target like "{instance}" and captures {"instance": "{http.request.host.labels.2}"}, returns "{http.request.host.labels.2}". For a literal target like "my-api", returns "my-api".
type Ingress ¶
type Ingress struct {
// ID is the unique identifier for this ingress (auto-generated).
ID string `json:"id"`
// Name is a human-readable name for the ingress.
Name string `json:"name"`
// Rules define the routing rules for this ingress.
Rules []IngressRule `json:"rules"`
// CreatedAt is the timestamp when this ingress was created.
CreatedAt time.Time `json:"created_at"`
}
Ingress represents an ingress resource that defines how external traffic should be routed to VM instances.
type IngressMatch ¶
type IngressMatch struct {
// Hostname is the hostname to match. Can be:
// - Literal: "api.example.com" (exact match on Host header)
// - Pattern: "{instance}.example.com" (dynamic, extracts subdomain as instance name)
// This is required.
Hostname string `json:"hostname"`
// Port is the host port to listen on for this rule.
// If not specified, defaults to 80.
Port int `json:"port,omitempty"`
}
IngressMatch specifies the conditions for matching incoming requests.
func (*IngressMatch) GetPort ¶
func (m *IngressMatch) GetPort() int
GetPort returns the port for this match, defaulting to 80 if not specified.
func (*IngressMatch) IsPattern ¶
func (m *IngressMatch) IsPattern() bool
IsPattern returns true if the hostname contains {name} captures.
func (*IngressMatch) ParsePattern ¶
func (m *IngressMatch) ParsePattern() (*HostnamePattern, error)
ParsePattern parses the hostname pattern and returns a HostnamePattern. For "{instance}.example.com":
- Wildcard: "*.example.com"
- Captures: ["instance"]
- CaddyLabels: {"instance": "{http.request.host.labels.2}"}
Caddy labels are indexed from the right (TLD first): - foo.bar.example.com → labels.0=com, labels.1=example, labels.2=bar, labels.3=foo
type IngressRule ¶
type IngressRule struct {
// Match specifies the conditions for matching incoming requests.
Match IngressMatch `json:"match"`
// Target specifies where matching requests should be routed.
Target IngressTarget `json:"target"`
// TLS enables TLS termination for this rule.
// When enabled, a certificate will be automatically issued via ACME.
TLS bool `json:"tls,omitempty"`
// RedirectHTTP creates an automatic HTTP to HTTPS redirect for this hostname.
// Only applies when TLS is enabled.
RedirectHTTP bool `json:"redirect_http,omitempty"`
}
IngressRule defines a single routing rule within an ingress.
type IngressTarget ¶
type IngressTarget struct {
// Instance is the name or ID of the target instance.
Instance string `json:"instance"`
// Port is the port on the target instance.
Port int `json:"port"`
}
IngressTarget specifies the target for routing matched requests.
type InstanceResolver ¶
type InstanceResolver interface {
// ResolveInstanceIP resolves an instance name or ID to its IP address.
// Returns the IP address and nil error if found, or an error if the instance
// doesn't exist, isn't running, or has no network.
ResolveInstanceIP(ctx context.Context, nameOrID string) (string, error)
// InstanceExists checks if an instance with the given name or ID exists.
InstanceExists(ctx context.Context, nameOrID string) (bool, error)
// ResolveInstance resolves an instance name, ID, or ID prefix to its canonical name and ID.
// Returns (name, id, nil) if found, or an error if the instance doesn't exist.
ResolveInstance(ctx context.Context, nameOrID string) (name string, id string, err error)
}
InstanceResolver provides instance resolution capabilities. This interface is implemented by the instance manager.
type Manager ¶
type Manager interface {
// Initialize starts the ingress subsystem.
// This should be called during server startup.
Initialize(ctx context.Context) error
// Create creates a new ingress resource.
Create(ctx context.Context, req CreateIngressRequest) (*Ingress, error)
// Get retrieves an ingress by ID, name, or ID prefix.
// Lookup order: exact ID match -> exact name match -> ID prefix match.
// Returns ErrAmbiguousName if prefix matches multiple ingresses.
Get(ctx context.Context, idOrName string) (*Ingress, error)
// List returns all ingress resources.
List(ctx context.Context) ([]Ingress, error)
// Delete removes an ingress resource by ID, name, or ID prefix.
// Lookup order: exact ID match -> exact name match -> ID prefix match.
// Returns ErrAmbiguousName if prefix matches multiple ingresses.
Delete(ctx context.Context, idOrName string) error
// Shutdown gracefully stops the ingress subsystem.
Shutdown(ctx context.Context) error
// AdminURL returns the Caddy admin API URL.
// Only valid after Initialize() has been called.
AdminURL() string
}
Manager is the interface for managing ingress resources.
func NewManager ¶
func NewManager(p *paths.Paths, config Config, instanceResolver InstanceResolver, otelLogger *slog.Logger) Manager
NewManager creates a new ingress manager. If otelLogger is non-nil, Caddy system logs will be forwarded to OTEL.
type ValidationError ¶
ValidationError represents a validation error.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string