90 lines
2.8 KiB
Go
90 lines
2.8 KiB
Go
/*
|
|
Copyright 2026.
|
|
|
|
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 instance contains the pure domain model of a registered PostgreSQL instance.
|
|
// It does not depend on Kubernetes types, database drivers or credential providers.
|
|
package instance
|
|
|
|
import (
|
|
"errors"
|
|
"net/netip"
|
|
"regexp"
|
|
)
|
|
|
|
// TLSMode is an explicit transport policy, not a driver-specific default.
|
|
type TLSMode string
|
|
|
|
const (
|
|
TLSDisable TLSMode = "disable"
|
|
TLSRequire TLSMode = "require"
|
|
TLSVerifyCA TLSMode = "verify-ca"
|
|
TLSVerifyFull TLSMode = "verify-full"
|
|
)
|
|
|
|
// EndpointValues carries explicit, effective values across the application boundary.
|
|
// Defaults are supplied by the API/application mapping, never silently by the domain.
|
|
type EndpointValues struct {
|
|
Host string
|
|
HostAddr string
|
|
Port int
|
|
ManagementDatabase string
|
|
TLSMode TLSMode
|
|
}
|
|
|
|
// Endpoint is an immutable connection target. Equality compares its declared values,
|
|
// not physical server identity. Its zero value is invalid; aggregate construction
|
|
// must Validate incoming endpoints, even if callers bypass NewEndpoint.
|
|
type Endpoint struct {
|
|
values EndpointValues
|
|
}
|
|
|
|
var identifier = regexp.MustCompile(`^[a-z][a-z0-9_]{0,62}$`)
|
|
|
|
func NewEndpoint(values EndpointValues) (Endpoint, error) {
|
|
endpoint := Endpoint{values: values}
|
|
if err := endpoint.Validate(); err != nil {
|
|
return Endpoint{}, err
|
|
}
|
|
return endpoint, nil
|
|
}
|
|
|
|
// Values returns a copy, without exposing mutable state.
|
|
func (e Endpoint) Values() EndpointValues { return e.values }
|
|
|
|
// Validate checks local invariants only; it does not resolve DNS or perform IO.
|
|
// Errors intentionally omit input values.
|
|
func (e Endpoint) Validate() error {
|
|
if e.values.Host == "" {
|
|
return errors.New("endpoint host is required")
|
|
}
|
|
address, err := netip.ParseAddr(e.values.HostAddr)
|
|
if err != nil || address.Zone() != "" {
|
|
return errors.New("endpoint host address must be an IPv4 or IPv6 address")
|
|
}
|
|
if e.values.Port < 1 || e.values.Port > 65535 {
|
|
return errors.New("endpoint port must be between 1 and 65535")
|
|
}
|
|
if !identifier.MatchString(e.values.ManagementDatabase) {
|
|
return errors.New("endpoint management database must be a valid PostgreSQL identifier")
|
|
}
|
|
switch e.values.TLSMode {
|
|
case TLSDisable, TLSRequire, TLSVerifyCA, TLSVerifyFull:
|
|
return nil
|
|
default:
|
|
return errors.New("endpoint TLS mode must be explicitly supported")
|
|
}
|
|
}
|