Documentation
¶
Overview ¶
Package server provides a cloud-agnostic server implementation for CUDly. It supports both AWS Lambda and standard HTTP server modes.
Index ¶
- func CreateHTTPServer(app *Application, port int) *http.Server
- func ParseScheduledEvent(rawEvent json.RawMessage) (ScheduledTaskType, ScheduledTaskParams, error)
- func StartHTTPServer(app *Application, port int) error
- func StartLambdaHandler(app *Application)
- type AnalyticsCollectorInterface
- type AnalyticsConfig
- type AnalyticsStoreInterface
- type Application
- func (app *Application) Close() error
- func (app *Application) HandleLambdaEvent(ctx context.Context, rawEvent json.RawMessage) (any, error)
- func (app *Application) HandleSQSMessage(ctx context.Context, body string) error
- func (app *Application) HandleScheduledTask(ctx context.Context, taskType ScheduledTaskType, params ScheduledTaskParams) (any, error)
- type ApplicationConfig
- type CheckResult
- type ExternalDeps
- type HealthStatus
- type LadderRunResult
- type PurchaseManagerInterface
- type ScheduledEvent
- type ScheduledTaskParams
- type ScheduledTaskType
- type SchedulerInterface
- type TaskLocker
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CreateHTTPServer ¶
func CreateHTTPServer(app *Application, port int) *http.Server
CreateHTTPServer builds the HTTP server with routes and timeouts configured, but does not start listening. This is useful for testing.
func ParseScheduledEvent ¶
func ParseScheduledEvent(rawEvent json.RawMessage) (ScheduledTaskType, ScheduledTaskParams, error)
ParseScheduledEvent parses a scheduled event and returns the task type plus any per-task parameters.
func StartHTTPServer ¶
func StartHTTPServer(app *Application, port int) error
StartHTTPServer starts the HTTP server with graceful shutdown on SIGINT/SIGTERM. It blocks until the server exits cleanly. In container orchestrators (Cloud Run, Container Apps, Fargate) SIGTERM is the normal stop signal; without this wiring the process is killed before deferred app.Close() runs, leaving in-flight requests cut and the DB pool/advisory locks undrained (issue #1025).
func StartLambdaHandler ¶
func StartLambdaHandler(app *Application)
StartLambdaHandler starts the AWS Lambda handler.
Types ¶
type AnalyticsCollectorInterface ¶
AnalyticsCollectorInterface aggregates current savings into a point-in-time snapshot row per (tenant, provider, service, region, commitment_type) bucket.
type AnalyticsConfig ¶
type AnalyticsConfig struct {
// Enabled gates the analytics_collect scheduled task. When false the task
// returns a "disabled" status without touching the DB. Default true.
Enabled bool
// RetentionMonths is how many months of snapshot partitions to keep before
// the retention job drops them. Default 24. Must be >= 1.
RetentionMonths int
// PartitionsAhead is how many future monthly partitions to keep provisioned
// ahead of the current month so inserts never fall into the catch-all
// default partition (M3). Default 3. Must be >= 1.
PartitionsAhead int
}
AnalyticsConfig holds the savings-snapshot collector knobs, read from env at startup and validated at the boundary (see Validate).
func LoadAnalyticsConfig ¶
func LoadAnalyticsConfig() AnalyticsConfig
LoadAnalyticsConfig reads the collector knobs from env, falling back to defaults for unset/blank values. Out-of-range or unparseable values are preserved as-is here so Validate can reject them with a clear message at startup (fail-fast at the boundary) rather than being silently clamped.
func (AnalyticsConfig) Validate ¶
func (c AnalyticsConfig) Validate() error
Validate rejects out-of-range analytics knobs so a misconfiguration fails fast at startup instead of silently producing a broken retention/partition policy at the first scheduled run.
type AnalyticsStoreInterface ¶
type AnalyticsStoreInterface interface {
RefreshMaterializedViews(ctx context.Context) error
// CreateFuturePartitions ensures partitions exist for the current month
// plus monthsAhead months ahead (M3: partitions otherwise stop after the
// seeded months and every insert falls into the catch-all default).
CreateFuturePartitions(ctx context.Context, monthsAhead int) error
// DropOldPartitions drops partitions older than retentionMonths (retention).
DropOldPartitions(ctx context.Context, retentionMonths int) error
}
AnalyticsStoreInterface defines the methods required for analytics storage. Beyond the materialized-view refresh, the scheduled analytics task also keeps monthly partitions provisioned ahead of time and applies retention.
type Application ¶
type Application struct {
Config config.StoreInterface
API *api.Handler
Scheduler SchedulerInterface
Purchase PurchaseManagerInterface
Email email.SenderInterface // Multi-cloud email sender (AWS SES, GCP SendGrid, Azure ACS)
Auth *auth.Service
RateLimiter api.RateLimiterInterface // Distributed rate limiter (DB-backed for multi-instance)
Analytics AnalyticsStoreInterface // Analytics store for savings data
// AnalyticsCollector aggregates savings into snapshots on a schedule.
// Nil until reinitializeAfterConnect wires it; the collect task no-ops
// when nil so test builds without a DB stay quiet.
AnalyticsCollector AnalyticsCollectorInterface
Version string
DB *database.Connection // PostgreSQL database connection
TaskLocker TaskLocker // Advisory lock for scheduled tasks (defaults to DB)
// LadderCapabilityFactory constructs a LadderCapability for the given region
// and accountID. It is called once per ladder_run task invocation.
// Defaults to awsladder.NewFromAWSConfig in production; tests replace it
// with a fake factory that returns a hermetic LadderCapability.
LadderCapabilityFactory func(ctx context.Context, region, accountID string) (pkgladder.LadderCapability, error)
// LadderAccountResolver resolves the Lambda's own AWS account ID and region
// for the single-account ladder gate (Q1). It MUST fail loud when the
// account cannot be determined: the account ID gates which configs run, so a
// transient STS failure must abort the whole ladder_run rather than silently
// skip every config as multi-account. Nil in production -> the default
// STS-backed resolver (defaultLadderAccountResolver); tests inject a stub.
LadderAccountResolver func(ctx context.Context) (accountID, region string, err error)
// contains filtered or unexported fields
}
Application holds all components of the CUDly server.
func NewApplication ¶
func NewApplication(ctx context.Context, version string) (*Application, error)
NewApplication creates and initializes a new Application instance. version overrides the VERSION env var when non-empty, so cmd entrypoints can pass the ldflags-stamped value directly instead of round-tripping through os.Setenv / os.Getenv (04-N1). Pass "" to fall back to the env.
func NewApplicationFromDeps ¶
func NewApplicationFromDeps(ctx context.Context, cfg ApplicationConfig, deps ExternalDeps) (*Application, error)
NewApplicationFromDeps creates an Application from pre-built configuration and dependencies. This is the testable constructor - all external I/O is done before calling this.
func (*Application) Close ¶
func (app *Application) Close() error
Close gracefully shuts down the application.
func (*Application) HandleLambdaEvent ¶
func (app *Application) HandleLambdaEvent(ctx context.Context, rawEvent json.RawMessage) (any, error)
HandleLambdaEvent processes any Lambda event type.
func (*Application) HandleSQSMessage ¶
func (app *Application) HandleSQSMessage(ctx context.Context, body string) error
HandleSQSMessage processes an SQS message for async purchase processing.
func (*Application) HandleScheduledTask ¶
func (app *Application) HandleScheduledTask(ctx context.Context, taskType ScheduledTaskType, params ScheduledTaskParams) (any, error)
HandleScheduledTask processes a scheduled task by type. It acquires a PostgreSQL advisory lock to prevent concurrent execution of the same task.
type ApplicationConfig ¶
type ApplicationConfig struct {
ScheduledTaskSecret string
IssuerURL string
ScheduledTaskSecretName string
DefaultPaymentOption string
Version string
DefaultRampSchedule string
CORSAllowedOrigin string
DashboardURL string
DashboardBucket string
APIKeySecretARN string
Analytics AnalyticsConfig
NotificationDaysBefore int
DefaultCoverage float64
DefaultTerm int
EnableDashboard bool
IsLambda bool
}
ApplicationConfig holds all env-based configuration for the application.
func LoadApplicationConfig ¶
func LoadApplicationConfig() ApplicationConfig
LoadApplicationConfig reads all configuration from environment variables.
type CheckResult ¶
CheckResult represents the result of a health check.
type ExternalDeps ¶
type ExternalDeps struct {
EmailSender email.SenderInterface
ConfigStore config.StoreInterface
DBConfig *database.Config
SecretResolver secrets.Resolver
STSClient purchase.STSClient
}
ExternalDeps holds pre-built external dependencies that require infrastructure.
type HealthStatus ¶
type HealthStatus struct {
Timestamp time.Time `json:"timestamp"`
Checks map[string]CheckResult `json:"checks"`
Status string `json:"status"`
Version string `json:"version"`
}
HealthStatus represents the overall health of the application.
type LadderRunResult ¶
type LadderRunResult struct {
Planned int `json:"planned"`
SkippedCadence int `json:"skipped_cadence"`
SkippedDisabled int `json:"skipped_disabled"`
SkippedMultiAccount int `json:"skipped_multi_account"`
Errored int `json:"errored"`
}
LadderRunResult is the aggregate outcome of one ladder_run task invocation. Each counter increments once per ladder_config entry processed.
type PurchaseManagerInterface ¶
type PurchaseManagerInterface interface {
ProcessScheduledPurchases(ctx context.Context) (*purchase.ProcessResult, error)
SendUpcomingPurchaseNotifications(ctx context.Context) (*purchase.NotificationResult, error)
ProcessMessage(ctx context.Context, body string) error
ApproveExecution(ctx context.Context, execID, token, actor string) error
ApproveAndExecute(ctx context.Context, execID, actor string, transitionedBy *string) error
CancelExecution(ctx context.Context, execID, token, actor string) error
// ReapStuckExecutions sweeps purchase_executions stuck in
// approved/running longer than reapAfter and flips them to "failed"
// via the existing TransitionExecutionStatus CAS. Wired into the
// "reap_stuck_purchases" scheduled task. See issue #678.
ReapStuckExecutions(ctx context.Context, reapAfter time.Duration) (*purchase.ReapResult, error)
// FireScheduledDelayedPurchases fires purchase_executions in status=scheduled
// whose scheduled_execution_at is in the past (Gmail-style pre-fire delay,
// issue #291 wave-2). Called on the "fire_scheduled_purchases" scheduler tick.
FireScheduledDelayedPurchases(ctx context.Context) (*purchase.FireResult, error)
// FinalizeInFlightRevocations sweeps purchase_history rows with
// revocation_in_flight=true and retries MarkPurchaseRevoked for each. Handles
// the partial-success case where Azure Return succeeded but the DB write
// failed (issue #290 Finding #6). Called on the "finalize_revocations" tick.
FinalizeInFlightRevocations(ctx context.Context) (*purchase.FinalizeResult, error)
}
PurchaseManagerInterface defines the methods required for the purchase manager component.
type ScheduledEvent ¶
type ScheduledEvent struct {
Source string `json:"source"`
DetailType string `json:"detail-type"`
Action string `json:"action"`
Detail json.RawMessage `json:"detail"`
// OwnerToken carries the collection-marker owner token (issue #261
// compare-and-clear guard) for TaskCollectRecommendations events fired
// by the async self-invoke in handler_recommendations_refresh.go.
// EventBridge cron deliveries and the --task CLI never set this field,
// so it decodes to "" and the scheduler treats the run as owning no
// marker (see ScheduledTaskParams / clearCollectionStartedBestEffort).
OwnerToken string `json:"owner_token"`
}
ScheduledEvent represents a generic scheduled event.
type ScheduledTaskParams ¶
type ScheduledTaskParams struct {
OwnerToken string
}
ScheduledTaskParams carries per-task-type data extracted from a ScheduledEvent. Only TaskCollectRecommendations currently reads a field (OwnerToken); other task types receive a zero-value struct.
type ScheduledTaskType ¶
type ScheduledTaskType string
ScheduledTaskType represents different types of scheduled tasks.
const ( TaskCollectRecommendations ScheduledTaskType = "collect_recommendations" TaskProcessScheduledPurchases ScheduledTaskType = "process_scheduled_purchases" TaskSendNotifications ScheduledTaskType = "send_notifications" TaskCleanupExpiredRecords ScheduledTaskType = "cleanup" TaskRefreshAnalytics ScheduledTaskType = "analytics_refresh" // TaskCollectAnalytics runs the savings-snapshot collector end to end: // ensure upcoming partitions, collect a snapshot across all tenants, apply // retention, and refresh the materialized views. Scheduled separately from // TaskRefreshAnalytics (the legacy refresh-only task) so the snapshot // ingestion cadence can differ from a pure view refresh. See issues // #1023 / #1033. TaskCollectAnalytics ScheduledTaskType = "analytics_collect" TaskRIExchangeReshape ScheduledTaskType = "ri_exchange_reshape" // TaskReapStuckPurchases sweeps purchase_executions stuck in // approved/running longer than PURCHASE_APPROVED_REAP_AFTER and flips // them to "failed" via the existing TransitionExecutionStatus CAS. // Backstop for synchronous-executor crashes (Lambda timeout, OOM, // network hang) that leave rows orphaned in an in-flight state. // See internal/purchase/reaper.go + issue #678. TaskReapStuckPurchases ScheduledTaskType = "reap_stuck_purchases" // TaskFireScheduledPurchases fires purchase_executions in status=scheduled // whose scheduled_execution_at is in the past (Gmail-style pre-fire delay, // issue #291 wave-2). Wires the "fire_scheduled_purchases" event action // to purchase.Manager.FireScheduledDelayedPurchases. TaskFireScheduledPurchases ScheduledTaskType = "fire_scheduled_purchases" // TaskFinalizeRevocations sweeps purchase_history rows with // revocation_in_flight=true and retries MarkPurchaseRevoked for each. // These rows represent partial-success cases where the Azure Return API call // succeeded but the subsequent DB write failed. The sweep ensures the audit // record is eventually consistent without requiring the user to retry (which // would be rejected by Azure). See issue #290 Finding #6. TaskFinalizeRevocations ScheduledTaskType = "finalize_revocations" // TaskLadderRun runs the commitment-laddering planner for every enabled // ladder_config. It computes a plan (Allocate + BuildTranches), persists the // ladder_run and ladder_tranches rows with status=planned, and stops. No // purchases, no emails, no reshapes, no approval tokens are issued in this // plan-only phase (PR-2). Execution arrives in a later PR. TaskLadderRun ScheduledTaskType = "ladder_run" )
type SchedulerInterface ¶
type SchedulerInterface interface {
CollectRecommendations(ctx context.Context, ownerToken string) (*scheduler.CollectResult, error)
ListRecommendations(ctx context.Context, filter config.RecommendationFilter) ([]config.RecommendationRecord, error)
// GetRecommendationByID fetches a single rec by application-level id,
// bypassing account-override filtering. hiddenBy is non-nil when the rec
// exists but would be dropped by the override filter. Returns nil, nil,
// nil when absent or fully suppressed.
GetRecommendationByID(ctx context.Context, id string) (rec *config.RecommendationRecord, hiddenBy []string, err error)
}
SchedulerInterface defines the methods required for the scheduler component.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package scheduledauth provides authentication for the /api/scheduled/* endpoints invoked by Cloud Scheduler / Logic Apps.
|
Package scheduledauth provides authentication for the /api/scheduled/* endpoints invoked by Cloud Scheduler / Logic Apps. |