177 lines
5.5 KiB
Go
177 lines
5.5 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"proxy-pool/internal/config"
|
|
"proxy-pool/internal/controller/admin"
|
|
"proxy-pool/internal/controller/distribution"
|
|
"proxy-pool/internal/controller/extraction"
|
|
"proxy-pool/internal/controller/operations"
|
|
controllerRuntime "proxy-pool/internal/controller/runtime"
|
|
"proxy-pool/internal/domain/activitypool"
|
|
extractionDomain "proxy-pool/internal/domain/extraction"
|
|
"proxy-pool/internal/platform/admission"
|
|
"proxy-pool/internal/platform/httpserver"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidOptions = errors.New("invalid controller bootstrap options")
|
|
ErrStartup = errors.New("controller startup failed")
|
|
)
|
|
|
|
type Options struct {
|
|
ConfigPath string
|
|
Resolver config.Resolver
|
|
Now func() time.Time
|
|
HTTP httpserver.Options
|
|
}
|
|
|
|
type activityStore interface {
|
|
extractionDomain.Store
|
|
activitypool.StateInventoryReader
|
|
}
|
|
|
|
type ports struct {
|
|
state admin.StateRepository
|
|
activity activityStore
|
|
readiness distribution.ReadinessChecker
|
|
close func() error
|
|
}
|
|
|
|
type infrastructure interface {
|
|
Open(context.Context, *config.Config) (ports, error)
|
|
}
|
|
|
|
type controllerRunner interface {
|
|
Run(context.Context) error
|
|
}
|
|
|
|
type runtimeFactory interface {
|
|
New(*config.Config, controllerRuntime.Dependencies, controllerRuntime.Options) (controllerRunner, error)
|
|
}
|
|
|
|
func Run(ctx context.Context, options Options) error {
|
|
return run(ctx, options, &productionInfrastructure{}, productionRuntimeFactory{})
|
|
}
|
|
|
|
func run(ctx context.Context, options Options, infrastructure infrastructure, factory runtimeFactory) (resultErr error) {
|
|
if ctx == nil || strings.TrimSpace(options.ConfigPath) != options.ConfigPath || options.ConfigPath == "" ||
|
|
nilInterface(options.Resolver) || nilInterface(infrastructure) || nilInterface(factory) {
|
|
return ErrInvalidOptions
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if options.Now == nil {
|
|
options.Now = time.Now
|
|
}
|
|
|
|
loader, err := admin.NewFileConfigurationLoader(options.ConfigPath, options.Resolver)
|
|
if err != nil {
|
|
return errors.Join(ErrInvalidOptions, err)
|
|
}
|
|
loaded, err := loader.LoadConfiguration(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: load configuration: %w", ErrStartup, err)
|
|
}
|
|
configurationStore, err := config.NewStore(loaded.Value)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: initialize configuration store: %w", ErrStartup, err)
|
|
}
|
|
|
|
opened, err := infrastructure.Open(ctx, loaded.Value)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: open infrastructure: %w", ErrStartup, err)
|
|
}
|
|
if opened.close == nil {
|
|
return errors.Join(ErrStartup, ErrInvalidOptions)
|
|
}
|
|
defer func() {
|
|
resultErr = errors.Join(resultErr, opened.close())
|
|
}()
|
|
|
|
dependencies := controllerRuntime.Dependencies{}
|
|
if loaded.Value.Distribution.Enabled {
|
|
if nilInterface(opened.activity) || nilInterface(opened.readiness) {
|
|
return errors.Join(ErrStartup, ErrInvalidOptions)
|
|
}
|
|
service, serviceErr := extraction.NewService(opened.activity, extractionPolicy(loaded.Value), admission.AllowAll{}, options.Now)
|
|
if serviceErr != nil {
|
|
return fmt.Errorf("%w: build extraction service: %w", ErrStartup, serviceErr)
|
|
}
|
|
dependencies.Extractor = service
|
|
dependencies.Readiness = opened.readiness
|
|
}
|
|
if loaded.Value.Admin.Enabled {
|
|
if nilInterface(opened.state) || nilInterface(opened.activity) {
|
|
return errors.Join(ErrStartup, ErrInvalidOptions)
|
|
}
|
|
statusReader, statusErr := operations.NewReader(configurationStore, opened.activity, options.Now)
|
|
if statusErr != nil {
|
|
return fmt.Errorf("%w: build operational status reader: %w", ErrStartup, statusErr)
|
|
}
|
|
service, serviceErr := admin.NewApplicationService(admin.ApplicationDependencies{
|
|
State: opened.state, Operations: statusReader, Configuration: loader, Publisher: configurationStore,
|
|
}, admin.ApplicationOptions{Now: options.Now})
|
|
if serviceErr != nil {
|
|
return fmt.Errorf("%w: build admin service: %w", ErrStartup, serviceErr)
|
|
}
|
|
if _, applyErr := service.ApplyConfiguration(ctx, admin.ReloadCommand{
|
|
RequestID: "controller-startup", ActorID: "proxy-controller",
|
|
}, loaded); applyErr != nil {
|
|
return fmt.Errorf("%w: commit startup configuration: %w", ErrStartup, applyErr)
|
|
}
|
|
dependencies.AdminService = service
|
|
}
|
|
|
|
runner, err := factory.New(configurationStore.Current(), dependencies, controllerRuntime.Options{HTTP: options.HTTP})
|
|
if err != nil {
|
|
return fmt.Errorf("%w: build HTTP runtime: %w", ErrStartup, err)
|
|
}
|
|
if nilInterface(runner) {
|
|
return errors.Join(ErrStartup, ErrInvalidOptions)
|
|
}
|
|
return runner.Run(ctx)
|
|
}
|
|
|
|
func extractionPolicy(configuration *config.Config) extraction.Policy {
|
|
configured := configuration.Distribution.Extraction
|
|
return extraction.Policy{
|
|
MaxCountPerRequest: configured.MaxCountPerRequest,
|
|
DefaultFulfillment: extractionDomain.Fulfillment(configured.Fulfillment),
|
|
MinRemainingTTL: configured.MinRemainingTTL.Value(),
|
|
MaxHealthCheckAge: configured.MaxHealthCheckAge.Value(),
|
|
ReserveForGateway: configured.ReserveForGateway,
|
|
IdempotencyTTL: configured.IdempotencyTTL.Value(),
|
|
}
|
|
}
|
|
|
|
func nilInterface(value any) bool {
|
|
if value == nil {
|
|
return true
|
|
}
|
|
reflected := reflect.ValueOf(value)
|
|
switch reflected.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
|
return reflected.IsNil()
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
type productionRuntimeFactory struct{}
|
|
|
|
func (productionRuntimeFactory) New(
|
|
configuration *config.Config,
|
|
dependencies controllerRuntime.Dependencies,
|
|
options controllerRuntime.Options,
|
|
) (controllerRunner, error) {
|
|
return controllerRuntime.New(configuration, dependencies, options)
|
|
}
|