stores

package
v0.2.0-alpha.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

pkg/stores

Store provider abstractions and overlay machinery used by the render-validate pipeline to evaluate hypothetical configuration changes without modifying live state.

Overview

The controller's renderer takes a StoreProvider rather than a raw map[string]Store so it can be handed:

  • A RealStoreProvider during normal reconciliation — backed directly by the live pkg/k8s/store instances.
  • An OverlayStoreProvider during webhook validation or proposal-validator runs — wraps the live providers with a ValidationContext of overlays so the proposed change appears in templates without modifying the actual stores.

The package also defines its own Store interface (structurally identical to pkg/k8s/types.Store) and the TypesStoreAdapter that bridges them. The two stay structurally identical but are kept apart by arch-go.yml so pkg/stores can be consumed by the templating pipeline without dragging in client-go.

Quick Start

Real provider (production)
import "gitlab.com/haproxy-haptic/haptic/pkg/stores"

real := stores.NewRealStoreProvider(map[string]stores.Store{
    "ingresses": ingressStore,
    "services":  serviceStore,
})

// Renderer uses real.GetStore("ingresses") to fetch the live store.
// (The interface method is GetStore, not Get — Get exists on Store, not on StoreProvider.)
Overlay provider (validation)
overlays := map[string]*stores.StoreOverlay{
    "ingresses": stores.NewStoreOverlayForCreate(proposedIngress),
}
ctx := stores.NewValidationContext(overlays).
    WithHTTPOverlay(httpOverlay) // optional, for HTTP content validation

overlay := stores.NewOverlayStoreProvider(real, ctx)
// overlay.GetStore("ingresses") now returns a *CompositeStore that includes
// proposedIngress on top of the live ingresses store. Stores without a
// matching overlay pass through unchanged.

Overlay Constructors

Constructor Use for
NewStoreOverlay() Empty overlay; populate manually
NewStoreOverlayForCreate(obj) Webhook CREATE — obj appears in the composite store
NewStoreOverlayForUpdate(obj) Webhook UPDATE — obj replaces the existing entry
NewStoreOverlayForDelete(ns, name) Webhook DELETE — entry is hidden in the composite store

The *CompositeStore returned by OverlayStoreProvider.GetStore is read-only — its Add / Update / Delete / Clear methods all return *ReadOnlyStoreError so a template that accidentally tries to write learns immediately rather than corrupting the live store. The provider itself doesn't expose mutation methods at all; the safety comes from the store it hands out.

ContentOverlay vs HTTPContentOverlay

ContentOverlay is the marker interface ("does this overlay carry pending changes?"). HTTPContentOverlay is the same plus three methods for resolving HTTP content lookups during validation. Splitting them lets pkg/stores consume HTTP overlays without importing pkg/httpstore (the latter implements HTTPContentOverlay via its own HTTPOverlay type).

See Also

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package stores provides abstractions for accessing and composing resource stores.

Package stores provides abstractions for accessing and composing resource stores.

This package defines:

  • StoreProvider: Interface for accessing stores by name
  • RealStoreProvider: Implementation that returns actual stores
  • ValidationContext: Unified container for K8s and HTTP overlays
  • OverlayStoreProvider: Implementation that applies ValidationContext overlays

These abstractions enable "what if" scenarios by allowing components to render and validate configurations with proposed changes without modifying actual state.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CompositeStore

type CompositeStore struct {
	// contains filtered or unexported fields
}

CompositeStore wraps a base store with an overlay, providing a unified view that reflects both the actual state and proposed changes.

Read operations (Get, List) return the merged view. Write operations are not supported and will return an error.

Thread Safety

CompositeStore itself is not thread-safe, but this is by design:

  • A new CompositeStore is created for each validation request (not shared across goroutines)
  • The base store must be thread-safe (k8s.Store uses sync.RWMutex internally)
  • The overlay must not be modified after construction (immutable during validation)

This design avoids synchronization overhead since each request gets its own instance.

func NewCompositeStore

func NewCompositeStore(base Store, overlay *StoreOverlay) *CompositeStore

NewCompositeStore creates a new CompositeStore.

Parameters:

  • base: The underlying store with actual state
  • overlay: The proposed changes to apply on top

func (*CompositeStore) Add

func (s *CompositeStore) Add(_ any, _ []string) error

Add is not supported on CompositeStore. CompositeStore is read-only; modifications should be made through the overlay.

func (*CompositeStore) Clear

func (s *CompositeStore) Clear() error

Clear is not supported on CompositeStore. CompositeStore is read-only; modifications should be made through the overlay.

func (*CompositeStore) Delete

func (s *CompositeStore) Delete(_ ...string) error

Delete is not supported on CompositeStore. CompositeStore is read-only; modifications should be made through the overlay.

func (*CompositeStore) Get

func (s *CompositeStore) Get(keys ...string) ([]any, error)

Get returns resources matching the given keys.

The result includes:

  • Resources from the base store (excluding deletions)
  • Modifications (replacing base resources with same keys)
  • Additions that match the keys

func (*CompositeStore) List

func (s *CompositeStore) List() ([]any, error)

List returns all resources from the merged view.

func (*CompositeStore) Update

func (s *CompositeStore) Update(_ any, _ []string) error

Update is not supported on CompositeStore. CompositeStore is read-only; modifications should be made through the overlay.

type ContentOverlay

type ContentOverlay interface {
	// IsEmpty returns true if the overlay contains no pending changes.
	IsEmpty() bool
}

ContentOverlay is a marker interface for overlay types.

Both K8s overlays (StoreOverlay) and HTTP overlays implement this interface, enabling unified handling in validation pipelines.

type HTTPContentOverlay

type HTTPContentOverlay interface {
	ContentOverlay

	// GetContent returns content for the given URL.
	// Returns pending content if available, otherwise accepted content.
	GetContent(url string) (string, bool)

	// PendingURLs returns the list of URLs with pending content.
	PendingURLs() []string

	// HasPendingURL returns true if the given URL has pending content.
	HasPendingURL(url string) bool
}

HTTPContentOverlay represents HTTP content changes awaiting validation.

This interface abstracts the HTTP overlay to avoid circular imports between pkg/stores and pkg/httpstore. The httpstore.HTTPOverlay type implements this.

type OverlayStoreProvider

type OverlayStoreProvider struct {
	// contains filtered or unexported fields
}

OverlayStoreProvider applies ValidationContext overlays to a base provider.

In addition to applying K8s overlays, this provider also exposes the HTTP overlay for use by the render service.

Usage:

ctx := NewValidationContext(k8sOverlays).WithHTTPOverlay(httpOverlay)
provider := NewOverlayStoreProvider(baseProvider, ctx)

// K8s stores automatically have overlays applied
store := provider.GetStore("ingress")  // Returns CompositeStore if overlay exists

// HTTP overlay is accessible for render service
httpOverlay := provider.GetHTTPOverlay()  // Returns HTTPContentOverlay

func NewOverlayStoreProvider

func NewOverlayStoreProvider(base StoreProvider, context *ValidationContext) *OverlayStoreProvider

NewOverlayStoreProvider creates a provider that applies ValidationContext overlays.

Parameters:

  • base: The underlying store provider
  • context: ValidationContext containing K8s and/or HTTP overlays

If context is nil, behaves identically to the base provider.

func (*OverlayStoreProvider) GetHTTPOverlay

func (p *OverlayStoreProvider) GetHTTPOverlay() HTTPContentOverlay

GetHTTPOverlay returns the HTTP content overlay from the ValidationContext.

Returns nil if:

  • ValidationContext is nil
  • No HTTP overlay was set

The render service uses this to determine whether to include pending HTTP content.

func (*OverlayStoreProvider) GetStore

func (p *OverlayStoreProvider) GetStore(name string) Store

GetStore returns a store for the given name.

If a K8s overlay exists for this store in the ValidationContext, returns a CompositeStore that applies the overlay on top of the base store. Otherwise, returns the base store unchanged.

func (*OverlayStoreProvider) StoreNames

func (p *OverlayStoreProvider) StoreNames() []string

StoreNames returns the names of all available stores.

func (*OverlayStoreProvider) Validate

func (p *OverlayStoreProvider) Validate() error

Validate checks that all K8s overlays reference valid stores in the base provider.

type ReadOnlyStoreError

type ReadOnlyStoreError struct {
	Operation string
}

ReadOnlyStoreError indicates an attempt to modify a read-only store.

func (*ReadOnlyStoreError) Error

func (e *ReadOnlyStoreError) Error() string

type RealStoreProvider

type RealStoreProvider struct {
	// contains filtered or unexported fields
}

RealStoreProvider provides access to actual (non-overlaid) stores.

This is the default implementation used during normal reconciliation.

func NewRealStoreProvider

func NewRealStoreProvider(stores map[string]Store) *RealStoreProvider

NewRealStoreProvider creates a new RealStoreProvider with the given stores.

func (*RealStoreProvider) GetStore

func (p *RealStoreProvider) GetStore(name string) Store

func (*RealStoreProvider) StoreNames

func (p *RealStoreProvider) StoreNames() []string

type Store

type Store interface {
	// Get retrieves all resources matching the provided index keys.
	Get(keys ...string) ([]any, error)

	// List returns all resources in the store.
	List() ([]any, error)

	// Add inserts a new resource into the store with the provided index keys.
	Add(resource any, keys []string) error

	// Update modifies an existing resource in the store.
	Update(resource any, keys []string) error

	// Delete removes a resource from the store using its index keys.
	Delete(keys ...string) error

	// Clear removes all resources from the store.
	Clear() error
}

Store defines the interface for storing and retrieving indexed resources.

This interface mirrors pkg/k8s/Store but is defined here to maintain package independence. Go's implicit interface satisfaction means any type implementing these methods (like k8s/Store) automatically satisfies this.

type StoreOverlay

type StoreOverlay struct {
	// Additions are new resources to add to the store.
	Additions []runtime.Object

	// Modifications are existing resources with updated content.
	Modifications []runtime.Object

	// Deletions are keys identifying resources to remove.
	// Uses NamespacedName for consistent key format.
	Deletions []ktypes.NamespacedName
	// contains filtered or unexported fields
}

StoreOverlay represents proposed changes to a store.

This enables "what if" scenarios: validate configurations with proposed changes without modifying the actual store state.

Resources are pre-converted to template-friendly format (floats to ints) at construction time to avoid repeated conversion during Get()/List() calls.

func NewStoreOverlay

func NewStoreOverlay() *StoreOverlay

NewStoreOverlay creates a new empty StoreOverlay.

func NewStoreOverlayForCreate

func NewStoreOverlayForCreate(obj runtime.Object) *StoreOverlay

NewStoreOverlayForCreate creates a StoreOverlay for a CREATE operation. The resource is pre-converted to template-friendly format.

func NewStoreOverlayForDelete

func NewStoreOverlayForDelete(namespace, name string) *StoreOverlay

NewStoreOverlayForDelete creates a StoreOverlay for a DELETE operation.

func NewStoreOverlayForUpdate

func NewStoreOverlayForUpdate(obj runtime.Object) *StoreOverlay

NewStoreOverlayForUpdate creates a StoreOverlay for an UPDATE operation. The resource is pre-converted to template-friendly format.

func (*StoreOverlay) AddAddition

func (o *StoreOverlay) AddAddition(obj runtime.Object)

AddAddition adds a resource to the additions list and pre-converts it. Use this method instead of directly modifying the Additions field to ensure the pre-converted slice stays in sync.

func (*StoreOverlay) AddModification

func (o *StoreOverlay) AddModification(obj runtime.Object)

AddModification adds a resource to the modifications list and pre-converts it. Use this method instead of directly modifying the Modifications field to ensure the pre-converted slice stays in sync.

func (*StoreOverlay) IsEmpty

func (o *StoreOverlay) IsEmpty() bool

IsEmpty returns true if the overlay contains no changes.

type StoreProvider

type StoreProvider interface {
	// GetStore returns the store for the given name.
	// Returns nil if no store exists with that name.
	GetStore(name string) Store

	// StoreNames returns the names of all available stores.
	StoreNames() []string
}

StoreProvider provides access to stores by name.

This abstraction decouples consumers from how stores are obtained, enabling composition and overlaying of proposed changes.

type TypesStoreAdapter

type TypesStoreAdapter struct {
	// Inner is the underlying store to delegate to.
	// It must implement Get, List, Add, Update, Delete, Clear methods
	// with the same signatures as stores.Store.
	Inner interface {
		Get(keys ...string) ([]any, error)
		List() ([]any, error)
		Add(resource any, keys []string) error
		Update(resource any, keys []string) error
		Delete(keys ...string) error
		Clear() error
	}
}

TypesStoreAdapter wraps any type implementing the Store interface methods.

This adapter is needed because k8s/types.Store and stores.Store are identical interfaces defined in different packages. Go's structural typing means concrete types implement both, but interface VALUES cannot be directly assigned across package boundaries.

Example:

typesStore := someManager.GetStore("ingresses") // returns types.Store
storesStore := &TypesStoreAdapter{Inner: typesStore} // now implements stores.Store

func (*TypesStoreAdapter) Add

func (a *TypesStoreAdapter) Add(resource any, keys []string) error

func (*TypesStoreAdapter) Clear

func (a *TypesStoreAdapter) Clear() error

func (*TypesStoreAdapter) Delete

func (a *TypesStoreAdapter) Delete(keys ...string) error

func (*TypesStoreAdapter) Get

func (a *TypesStoreAdapter) Get(keys ...string) ([]any, error)

func (*TypesStoreAdapter) List

func (a *TypesStoreAdapter) List() ([]any, error)

func (*TypesStoreAdapter) Update

func (a *TypesStoreAdapter) Update(resource any, keys []string) error

type ValidationContext

type ValidationContext struct {
	// K8sOverlays maps store names to their proposed changes.
	K8sOverlays map[string]*StoreOverlay

	// HTTPOverlay contains pending HTTP content changes.
	// Nil means no HTTP content changes to validate.
	HTTPOverlay HTTPContentOverlay
}

ValidationContext is a unified container for all overlay types.

It holds both K8s store overlays and HTTP content overlays, enabling the render pipeline to apply proposed changes transparently during validation.

Usage:

// For K8s-only validation (webhook admission)
ctx := NewValidationContext(k8sOverlays)

// For HTTP content validation
ctx := NewValidationContext(nil).WithHTTPOverlay(httpOverlay)

// For combined validation
ctx := NewValidationContext(k8sOverlays).WithHTTPOverlay(httpOverlay)

func NewValidationContext

func NewValidationContext(overlays map[string]*StoreOverlay) *ValidationContext

NewValidationContext creates a ValidationContext with K8s overlays.

Parameters:

  • overlays: Map of store name to proposed K8s changes (can be nil)

Returns a ValidationContext ready for use or further configuration.

func (*ValidationContext) IsEmpty

func (c *ValidationContext) IsEmpty() bool

IsEmpty returns true if the context contains no overlays.

A context is empty if it has no K8s overlays AND no HTTP overlay.

func (*ValidationContext) WithHTTPOverlay

func (c *ValidationContext) WithHTTPOverlay(overlay HTTPContentOverlay) *ValidationContext

WithHTTPOverlay adds an HTTP overlay to the validation context.

Returns the same ValidationContext for method chaining:

ctx := NewValidationContext(overlays).WithHTTPOverlay(httpOverlay)

Directories

Path Synopsis
Package storetest provides shared mock implementations of stores.Store for testing.
Package storetest provides shared mock implementations of stores.Store for testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL