Architecture
Kure Architecture Documentation
Executive Summary
Kure is a Go library for programmatically building Kubernetes resources used by GitOps tools (Flux, cert-manager, MetalLB, External Secrets). The library emphasizes strongly-typed object construction over templating engines, providing a composable, type-safe approach to generating Kubernetes manifests.
Key Architectural Achievements:
- Domain-Driven Design: Hierarchical cluster model with clear boundaries
- Interface Segregation: Split monolithic workflow interfaces into focused components
- Type Safety: Strong typing throughout with comprehensive validation
- GitOps Agnostic: Support for multiple GitOps tools through pluggable workflows
- Declarative Patching: JSONPath-based patching system with structure preservation (moved to go-kure/launcher)
The architecture supports complex Kubernetes cluster configurations while maintaining simplicity and extensibility through clean separation of concerns and well-defined interfaces.
Table of Contents
- Architecture Overview
- Domain Model Architecture
- Workflow Architecture
- Error Handling Architecture
- Resource Builder Pattern
- Patch System Architecture
- Layout and Packaging
- Naming Conventions
- Developer Guidelines
- Performance Characteristics
- Security Model
- Testing Architecture
- Appendices
Architecture Overview
System Boundaries
Kure operates within the Kubernetes ecosystem as a library for programmatic resource generation:
graph TB
subgraph "Kure Library"
DM[Domain Model]
WF[Workflow Engines]
RB[Resource Builders]
LO[Layout Engine]
end
subgraph "GitOps Tools"
FLUX[Flux]
ARGO[ArgoCD]
end
subgraph "Kubernetes"
K8S[Core Resources]
CRD[Custom Resources]
end
USER[User Code] --> DM
DM --> WF
WF --> RB
RB --> K8S
RB --> CRD
WF --> LO
LO --> FLUX
LO --> ARGO
PS --> K8SCore Components
The system is organized around four primary architectural layers:
- Domain Model (
pkg/stack/): Hierarchical abstractions for cluster configuration - Workflow Engines (
pkg/stack/workflow.go,pkg/stack/fluxcd/,pkg/stack/argocd/): GitOps-specific implementations - Resource Builders (
internal/): Strongly-typed Kubernetes resource factories - Support Systems: Error handling, layout, and I/O utilities
What kure does NOT provide
kure is an unopinionated foundation. It provides building blocks — it does not compose them into named application patterns or OAM abstractions.
| Not in kure | Reason |
|---|---|
| Application-level components (webservice, worker, helmrelease) | Downstream consumers have different opinions on what each means |
| Trait logic (ingress, certificate, external-secret) | Trait implementation depends on platform capabilities |
| OAM model (Application, Component, Trait, Policy) | This belongs in the OAM runtime layer (launcher) |
| Policy enforcement | Enforcement rules are organizational — not a library concern |
| GitOps delivery layout decisions | Each consumer defines its own OCI artifact hierarchy |
Why this matters for library design. If kure defined a WebserviceConfig, it would need to
decide: does it include a ServiceAccount? Topology spread constraints? Sidecars? Each downstream
consumer has different answers. Putting the composed abstraction in the library couples all
consumers to kure’s version of that answer. kure avoids this by providing composable primitives and
leaving composition to consumers.
Key Design Principles
1. Composition Over Inheritance
- Domain objects compose behavior through interfaces
- Workflow engines compose specialized generators
- Resources built through functional composition
2. Interface Segregation
- Small, focused interfaces for specific concerns
- Workflow interfaces split by responsibility
- Clear separation between resource generation and layout
3. Immutable Constructs
- Builder pattern creates immutable objects
- Patching creates new instances rather than modifying
- Functional approach to resource transformation
4. Type Safety
- Strong typing for all Kubernetes resources
- Compile-time validation of resource construction
- Custom error types with contextual information
Relationship to Launcher
launcher is an OAM-native package manager built on kure. The dependency is strictly one-directional: launcher imports kure; kure has no dependency on launcher.
downstream consumers
│
▼
launcher (OAM runtime)
│
▼
kure (library)kure provides the building blocks — ApplicationConfig interface, K8s resource builders, FluxCD
workflow primitives. Launcher uses these to implement an OAM-to-manifest pipeline with component
handlers, trait handlers, and a Policy extension point for downstream enforcement.
Downstream consumers that need capabilities beyond launcher’s built-in handlers can register
additional handlers and implement the launcher.Policy interface, while still using kure’s
resource builders directly.
For the full layering model see kure-launcher-architecture .
Domain Model Architecture
Hierarchical Structure
The domain model follows a four-tier hierarchy designed to mirror real-world Kubernetes cluster organization:
Cluster
└── Node (Infrastructure/Applications)
└── Bundle (Logical grouping)
└── Application (Individual workloads)Cluster (pkg/stack/cluster.go)
The root abstraction representing a complete Kubernetes cluster configuration:
type Cluster struct {
Name string `yaml:"name"`
Node *Node `yaml:"node,omitempty"`
GitOps *GitOpsConfig `yaml:"gitops,omitempty"`
}Design Decisions:
- Single root node simplifies tree traversal
- GitOps configuration at cluster level for global policies
- Name field provides unique identification across environments
Node (pkg/stack/cluster.go:47-64)
Hierarchical containers for organizing related bundles:
type Node struct {
Name string `yaml:"name"`
ParentPath string `yaml:"parentPath,omitempty"`
Children []*Node `yaml:"children,omitempty"`
PackageRef *schema.GroupVersionKind `yaml:"packageref,omitempty"`
Bundle *Bundle `yaml:"bundle,omitempty"`
// Runtime fields (not serialized)
parent *Node `yaml:"-"`
pathMap map[string]*Node `yaml:"-"`
}Anti-Circular Reference Design:
ParentPathstring instead of direct parent pointer in serialized form- Runtime
parentfield populated duringInitializePathMap() - Enables serialization while maintaining navigation efficiency
Bundle (pkg/stack/bundle.go)
Deployment units typically corresponding to single GitOps resources:
type Bundle struct {
Name string `yaml:"name"`
ParentPath string `yaml:"parentPath,omitempty"`
DependsOn []*Bundle `yaml:"dependsOn,omitempty"`
Applications []*Application `yaml:"applications"`
SourceRef *SourceRef `yaml:"sourceRef,omitempty"`
}Application (pkg/stack/application.go)
Individual Kubernetes workloads or resource collections:
type Application struct {
Name string `yaml:"name"`
Resources []client.Object `yaml:"resources"`
Labels map[string]string `yaml:"labels,omitempty"`
}Hierarchy Navigation
The domain model implements efficient tree traversal through a dual approach:
1. Path-Based Navigation
func (n *Node) GetPath() string {
if n.ParentPath == "" {
return n.Name
}
return n.ParentPath + "/" + n.Name
}2. Runtime Parent References
func (n *Node) InitializePathMap() {
pathMap := make(map[string]*Node)
n.buildPathMap(pathMap, "")
n.setPathMapRecursive(pathMap)
}This design enables:
- Efficient serialization without circular references
- Fast runtime navigation through cached parent pointers
- Path-based lookups for configuration references
Workflow Architecture
Interface Segregation Pattern
The workflow architecture implements Interface Segregation Principle by splitting monolithic interfaces into focused components:
// pkg/stack/workflow.go
type ResourceGenerator interface {
GenerateFromCluster(*stack.Cluster) ([]client.Object, error)
GenerateFromNode(*stack.Node) ([]client.Object, error)
GenerateFromBundle(*stack.Bundle) ([]client.Object, error)
}
type LayoutIntegrator interface {
IntegrateWithLayout(*layout.ManifestLayout, *stack.Cluster, layout.LayoutRules) error
CreateLayoutWithResources(*stack.Cluster, layout.LayoutRules) (*layout.ManifestLayout, error)
}
type BootstrapGenerator interface {
GenerateBootstrap(*stack.BootstrapConfig, *stack.Node) ([]client.Object, error)
SupportedBootstrapModes() []string
}
type WorkflowEngine interface {
ResourceGenerator
LayoutIntegrator
BootstrapGenerator
GetName() string
GetVersion() string
}FluxCD Implementation
The FluxCD workflow engine demonstrates the composition pattern:
// pkg/stack/fluxcd/workflow_engine.go
type WorkflowEngine struct {
ResourceGen *ResourceGenerator // Pure resource generation
LayoutInteg *LayoutIntegrator // Layout integration
BootstrapGen *BootstrapGenerator // Bootstrap concerns
}
func NewWorkflowEngine() *WorkflowEngine {
resourceGen := NewResourceGenerator()
layoutInteg := NewLayoutIntegrator(resourceGen)
bootstrapGen := NewBootstrapGenerator()
return &WorkflowEngine{
ResourceGen: resourceGen,
LayoutInteg: layoutInteg,
BootstrapGen: bootstrapGen,
}
}Component Responsibilities
ResourceGenerator (pkg/stack/fluxcd/resource_generator.go)
- Pure resource generation from domain objects
- Kustomization creation with proper source references
- Dependency management between bundles
- No layout or file system concerns
LayoutIntegrator (pkg/stack/fluxcd/layout_integrator.go)
- Integration with manifest layout system
- Directory structure generation
- File placement policies
- GitOps-specific layout requirements
BootstrapGenerator (pkg/stack/fluxcd/bootstrap_generator.go)
- Bootstrap resource generation
- GitOps system initialization
- Mode-specific configurations (gitops-toolkit vs flux-operator)
Extensibility Pattern
Adding new GitOps workflows follows a clear pattern:
- Implement Core Interfaces: ResourceGenerator, LayoutIntegrator, BootstrapGenerator
- Compose WorkflowEngine: Combine specialized generators
- Register with Layout: Add layout rules for the new workflow
- Provide Public API: Create user-facing convenience functions
Error Handling Architecture
KureError System
Kure implements a sophisticated error handling system based on typed errors with contextual information:
// pkg/errors/errors.go
type KureError interface {
error
Type() ErrorType
Suggestion() string
Context() map[string]interface{}
}
type ErrorType string
const (
ErrorTypeValidation ErrorType = "validation"
ErrorTypeResource ErrorType = "resource"
ErrorTypePatch ErrorType = "patch"
ErrorTypeParse ErrorType = "parse"
ErrorTypeFile ErrorType = "file"
ErrorTypeConfiguration ErrorType = "configuration"
ErrorTypeInternal ErrorType = "internal"
)Error Type Architecture
ValidationError (pkg/errors/errors.go:155-185)
- Field-level validation failures
- Provides valid value suggestions
- Component context for debugging
ResourceError (pkg/errors/errors.go:188-250)
- Resource-specific errors (not found, validation failed)
- Includes resource type, name, and namespace
- Lists available alternatives when applicable
PatchError (pkg/errors/errors.go:253-294)
- Patch operation failures
- Path and operation context
- Graceful degradation suggestions
ParseError (pkg/errors/errors.go:297-340)
- File parsing errors with location information
- Line and column numbers
- Format-specific help suggestions
Error Wrapping Strategy
Kure follows Go’s error wrapping conventions while adding structured context:
func (we *WorkflowEngine) GenerateFromCluster(c *stack.Cluster) ([]client.Object, error) {
if c == nil {
return nil, errors.ResourceValidationError("Cluster", "", "cluster",
"cluster cannot be nil", nil)
}
resources, err := we.ResourceGen.GenerateFromCluster(c)
if err != nil {
return nil, errors.Wrapf(err, "failed to generate resources for cluster %s", c.Name)
}
return resources, nil
}Resource Builder Pattern
Builder Architecture
Resource builders follow a consistent functional pattern across all Kubernetes resource types:
// Pattern: Create* functions for constructors
func CreateDeployment(name, namespace string) *appsv1.Deployment
// Pattern: Add* functions for collection modifications
func AddDeploymentContainer(deployment *appsv1.Deployment, container *corev1.Container) error
// Pattern: Set* functions for field assignments
func SetDeploymentReplicas(deployment *appsv1.Deployment, replicas int32) errorImplementation Structure
Each resource builder package (internal/kubernetes/, internal/fluxcd/, etc.) follows consistent organization:
internal/kubernetes/
├── doc.go # Package documentation
├── deployment.go # Deployment builders
├── deployment_test.go # Deployment tests
├── service.go # Service builders
├── service_test.go # Service tests
└── ...Type Safety Guarantees
Builders provide compile-time type safety through:
- Strong Return Types: All constructors return specific Kubernetes types
- Void Helpers: Setter/adder functions use void returns (no nil-checking) since callers always use constructors first
- Validation at Boundaries: Only public facade functions (
pkg/kubernetes/) validate inputs
Example implementation:
// internal/kubernetes/deployment.go
func CreateDeployment(name, namespace string) *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": name,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{},
},
},
},
}
}
func AddDeploymentContainer(deployment *appsv1.Deployment, container *corev1.Container) {
deployment.Spec.Template.Spec.Containers = append(
deployment.Spec.Template.Spec.Containers, *container)
}Cross-Resource Consistency
All builders maintain consistency through:
- Void Returns: All internal setter/adder functions use void returns consistently
- Standard Patterns: Uniform function naming across resource types
- Boundary Validation: Public facade functions handle nil-config checks
One-of Constraints (Sealed Interfaces)
Some upstream CRDs encode an exactly-one-of constraint as a struct with multiple optional pointer fields — the user is expected to set exactly one. Examples: cert-manager IssuerSpec (ACME / CA / Vault / SelfSigned / Venafi); VolSync ReplicationSourceSpec (Restic / Rsync / RsyncTLS / Rclone / Syncthing / External). Go’s type system can’t statically express “set exactly one of these fields”, so the constraint is a CRD-level (apply-time) check.
Kure encodes these as a sealed-interface sum type so violations are a compile error rather than an apply-time error. Pattern:
- Sealed marker interface with an unexported method, so only types in the same package can satisfy it:
type SourceMover interface { isSourceMover() } - Per-variant Configs as defined types over the upstream specs (or hand-rolled structs where simplification adds value), each attaching the marker:
type SourceResticConfig volsyncv1alpha1.ReplicationSourceResticSpec func (*SourceResticConfig) isSourceMover() {} type SourceRcloneConfig volsyncv1alpha1.ReplicationSourceRcloneSpec func (*SourceRcloneConfig) isSourceMover() {} // ... etc. - Single field of the interface type on the parent Config — the compiler enforces “at most one variant”:
type ReplicationSourceConfig struct { Name, Namespace string SourcePVC string Trigger *TriggerConfig Mover SourceMover // exactly one variant } - Type-switch dispatch in the public constructor:
switch m := cfg.Mover.(type) { case *SourceResticConfig: spec := volsyncv1alpha1.ReplicationSourceResticSpec(*m) rs.Spec.Restic = &spec case *SourceRcloneConfig: // ... }
This is the kure idiom for one-of: setting two variants is a compile error (single field), and missing variants are caught at construction (nil case in the type switch).
pkg/kubernetes/volsync and pkg/kubernetes/certmanager both follow this idiom. cert-manager carries three layers: IssuerVariant (ACME / CA on IssuerConfig.Variant and ClusterIssuerConfig.Variant), ACMESolver (HTTP-01 / DNS-01 on ACMESolverConfig.Solver), and DNS01Provider (Cloudflare / Route 53 / Google CloudDNS on DNS01SolverConfig.Provider).
Patch System Architecture
Note (2026-05-15):
pkg/patchmoved to go-kure/launcher as part of the launcher extraction (ADR-018). See the launcher repository for current patch system documentation.
Layout and Packaging
Layout Architecture
The layout system manages directory structure and manifest organization:
// pkg/stack/layout/types.go
type ManifestLayout struct {
Root string // Repository root path
Clusters map[string]*ClusterLayout // Per-cluster layouts
Global *GlobalLayout // Shared resources
}
type LayoutRules struct {
BundleGrouping GroupingStrategy // How to group bundles
ApplicationGrouping GroupingStrategy // How to group applications
KustomizationMode KustomizationMode // Kustomization generation
}Grouping Strategies
GroupFlat: Each item gets its own directory
clusters/prod/
├── bundles/
│ ├── monitoring/
│ ├── logging/
│ └── ingress/
└── apps/
├── frontend/
├── backend/
└── database/GroupByParent: Items grouped under parent directories
clusters/prod/
├── infrastructure/
│ ├── monitoring/
│ ├── logging/
│ └── ingress/
└── applications/
├── frontend/
├── backend/
└── database/GitOps Integration
Layout integrates with GitOps tools through specialized placement:
Flux Placement (pkg/stack/layout/config.go)
- Kustomization resources placed in flux-system namespace
- Source references use relative paths (
./clusters/prod/...) - Automatic kustomization.yaml generation
ArgoCD Placement
- Application resources in argocd namespace
- Source paths without
./prefix - Manual kustomization.yaml required
Directory Structure Generation
// pkg/stack/layout/walker.go
func WalkCluster(cluster *stack.Cluster, rules LayoutRules) (*ManifestLayout, error) {
layout := &ManifestLayout{
Root: ".",
Clusters: make(map[string]*ClusterLayout),
}
clusterLayout := &ClusterLayout{
Name: cluster.Name,
Path: filepath.Join("clusters", cluster.Name),
}
// Walk node hierarchy
if err := walkNode(cluster.Node, clusterLayout, rules); err != nil {
return nil, err
}
layout.Clusters[cluster.Name] = clusterLayout
return layout, nil
}Naming Conventions
Function Naming Standards
Kure follows strict naming conventions based on function purpose:
Constructor Functions
// Go type constructors use New* prefix
func NewCluster(name string, tree *Node) *Cluster
func NewBundle(name string, resources []*Application, labels map[string]string) (*Bundle, error)
// Kubernetes resource factories use descriptive names
func CreateDeployment(name, namespace string) *appsv1.Deployment
func CreateService(name, namespace string) *corev1.ServiceHelper Functions
// Adders for collection modifications
func AddDeploymentContainer(deployment *appsv1.Deployment, container *corev1.Container) error
func AddServicePort(service *corev1.Service, port corev1.ServicePort) error
// Setters for field assignments
func SetDeploymentReplicas(deployment *appsv1.Deployment, replicas int32) error
func SetServiceType(service *corev1.Service, serviceType corev1.ServiceType) errorWorkflow Functions
// Engine constructors follow New* pattern
func NewWorkflowEngine() *WorkflowEngine
func NewResourceGenerator() *ResourceGenerator
// Public APIs use descriptive names
func Engine() *WorkflowEngine // Default engine
func EngineWithMode(mode layout.KustomizationMode) *WorkflowEngine // Configured enginePackage Organization Standards
pkg/ # Public APIs and interfaces
├── stack/ # Domain model (public)
│ ├── fluxcd/ # FluxCD workflow implementation
│ ├── argocd/ # ArgoCD workflow implementation
│ └── layout/ # Layout generation utilities
├── stack/workflow.go # Workflow interfaces (public)
├── errors/ # Error handling utilities (public)
└── patch/ # Patch system (public) — moved to go-kure/launcher (ADR-018)
internal/ # Implementation packages (private)
├── kubernetes/ # Core Kubernetes builders
├── fluxcd/ # Flux resource builders
├── certmanager/ # cert-manager builders
├── metallb/ # MetalLB builders
└── externalsecrets/ # External Secrets buildersFile Naming Patterns
- Implementation files:
{resource_type}.go(e.g.,deployment.go,service.go) - Test files:
{resource_type}_test.go(e.g.,deployment_test.go) - Documentation:
doc.gofor package documentation - Design documents:
DESIGN.md,README.mdin relevant packages
Developer Guidelines
Adding New Resource Builders
Follow this standardized process for adding Kubernetes resource support:
1. Create Constructor Function
// internal/kubernetes/newresource.go
func CreateNewResource(name, namespace string, opts ...Option) *v1.NewResource {
resource := &v1.NewResource{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1.NewResourceSpec{
// Initialize required fields
},
}
// Apply options
for _, opt := range opts {
opt(resource)
}
return resource
}2. Add Helper Functions
func AddNewResourceField(resource *v1.NewResource, field FieldType) {
resource.Spec.Fields = append(resource.Spec.Fields, field)
}
func SetNewResourceProperty(resource *v1.NewResource, value PropertyType) {
resource.Spec.Property = value
}3. Comprehensive Testing
// internal/kubernetes/newresource_test.go
func TestCreateNewResource(t *testing.T) {
resource := CreateNewResource("test", "default")
if resource == nil {
t.Fatal("expected non-nil resource")
}
// Validate required fields
if resource.Name != "test" {
t.Errorf("expected name 'test', got %s", resource.Name)
}
if resource.Namespace != "default" {
t.Errorf("expected namespace 'default', got %s", resource.Namespace)
}
}
func TestNewResourceHelpers(t *testing.T) {
resource := CreateNewResource("test", "default")
// Test all helper functions
field := FieldType{/* valid field */}
AddNewResourceField(resource, field)
// Validate field was added
if len(resource.Spec.Fields) != 1 {
t.Errorf("expected 1 field, got %d", len(resource.Spec.Fields))
}
}Extending Domain Model
When extending the core domain model:
1. Maintain Hierarchy Consistency
// Add new domain types following existing patterns
type NewDomainType struct {
Name string `yaml:"name"`
ParentPath string `yaml:"parentPath,omitempty"`
// Domain-specific fields
// Runtime navigation (not serialized)
parent *ParentType `yaml:"-"`
pathMap map[string]*NewType `yaml:"-"`
}2. Implement Navigation Methods
func (n *NewDomainType) SetParent(parent *ParentType) {
n.parent = parent
if parent == nil {
n.ParentPath = ""
} else {
n.ParentPath = parent.GetPath()
}
}
func (n *NewDomainType) GetPath() string {
if n.ParentPath == "" {
return n.Name
}
return n.ParentPath + "/" + n.Name
}3. Update Workflow Implementations
Ensure all workflow engines handle the new domain type appropriately.
Implementing New GitOps Workflows
To add support for new GitOps tools:
1. Implement Core Interfaces
// pkg/stack/newtool/resource_generator.go
type ResourceGenerator struct {
// Tool-specific configuration
}
func (rg *ResourceGenerator) GenerateFromCluster(c *stack.Cluster) ([]client.Object, error) {
// Tool-specific resource generation
}
// Implement other ResourceGenerator methods2. Create Layout Integration
// pkg/stack/newtool/layout_integrator.go
type LayoutIntegrator struct {
ResourceGen *ResourceGenerator
// Tool-specific layout configuration
}
func (li *LayoutIntegrator) IntegrateWithLayout(ml *layout.ManifestLayout, c *stack.Cluster, rules layout.LayoutRules) error {
// Tool-specific layout integration
}3. Compose Workflow Engine
// pkg/stack/newtool/workflow_engine.go
type WorkflowEngine struct {
ResourceGen *ResourceGenerator
LayoutInteg *LayoutIntegrator
BootstrapGen *BootstrapGenerator
}
func NewWorkflowEngine() *WorkflowEngine {
// Compose components
}
// Implement workflow.WorkflowEngine interface4. Add Public API
// pkg/stack/newtool/newtool.go
func Engine() *WorkflowEngine {
return NewWorkflowEngine()
}Testing Patterns
Kure maintains comprehensive test coverage through consistent patterns:
Unit Testing
func TestResourceCreation(t *testing.T) {
// Test constructor
resource := CreateResource("test", "default")
// Validate required fields
// Test error conditions
// Verify helper functions
}
func TestResourceValidation(t *testing.T) {
// Test validation logic
// Test error cases
// Verify error messages
}Integration Testing
func TestWorkflowGeneration(t *testing.T) {
// Create domain model
cluster := stack.NewCluster("test", rootNode)
// Generate with workflow
engine := fluxcd.Engine()
resources, err := engine.GenerateFromCluster(cluster)
// Validate generated resources
// Test layout integration
}Facade Testing
func TestFacadeNilConfig(t *testing.T) {
// Test nil config returns nil object
obj := NewResource(nil)
if obj != nil {
t.Error("expected nil for nil config")
}
}Performance Characteristics
Resource Generation Performance
Kure is optimized for batch resource generation rather than individual operations:
Benchmarks (typical 100-node cluster):
- Domain model creation: ~1ms
- Resource generation: ~10ms
- Layout generation: ~5ms
- YAML serialization: ~15ms
Memory Usage:
- Domain model: ~100KB per 100 resources
- Generated resources: ~1MB per 1000 resources
- Layout structures: ~50KB per cluster
Optimization Strategies
1. Lazy Initialization
func (n *Node) InitializePathMap() {
// Only build path map when needed
if n.pathMap == nil {
pathMap := make(map[string]*Node)
n.buildPathMap(pathMap, "")
n.setPathMapRecursive(pathMap)
}
}2. Batch Operations
func (we *WorkflowEngine) GenerateFromCluster(c *stack.Cluster) ([]client.Object, error) {
// Generate all resources in single pass
// Minimize allocation overhead
}Bottlenecks and Mitigations
Known Bottlenecks:
- YAML serialization (mitigated by streaming output)
- Path resolution in complex hierarchies (mitigated by path caching)
- Resource generation overhead (mitigated by void builder functions)
Scaling Characteristics:
- Linear scaling with number of resources
- Logarithmic scaling with hierarchy depth
- Constant memory overhead per resource type
Security Model
Secret Management
Kure follows Kubernetes security best practices for secret handling:
1. No Hardcoded Secrets
// NEVER do this
func CreateSecretWithData(name, namespace, password string) *corev1.Secret {
return &corev1.Secret{
Data: map[string][]byte{
"password": []byte(password), // WRONG: hardcoded secret
},
}
}
// CORRECT approach - reference existing secrets
func CreateCertificateWithSecret(name, namespace string, secretRef cmmeta.SecretKeySelector) *cmv1.Certificate {
return &cmv1.Certificate{
Spec: cmv1.CertificateSpec{
SecretName: secretRef.Name,
// Reference, don't embed
},
}
}2. Secret Reference Pattern
// Standard pattern for secret references
key := cmmeta.SecretKeySelector{
LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret-name"},
Key: "key-name",
}
// Use in resource builders
cert := certmanager.CreateCertificate("tls-cert", "default")
certmanager.SetCertificateIssuerSecret(cert, key)RBAC Integration
Resource builders provide granular RBAC control:
// Create minimal privilege roles
role := kubernetes.CreateRole("app-reader", "default")
kubernetes.AddRoleRule(role, rbacv1.PolicyRule{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list"},
})
// Bind to specific accounts
binding := kubernetes.CreateRoleBinding("app-reader", "default")
kubernetes.SetRoleBindingRole(binding, "app-reader")
kubernetes.AddRoleBindingSubject(binding, rbacv1.Subject{
Kind: "ServiceAccount",
Name: "app-sa",
})Certificate Management
cert-manager integration provides secure TLS:
// ACME challenge configuration
issuer := certmanager.CreateClusterIssuer("letsencrypt")
certmanager.SetClusterIssuerACME(issuer, "https://acme-v02.api.letsencrypt.org/directory")
certmanager.AddClusterIssuerACMEDNS01Provider(issuer, "cloudflare", map[string]string{
"email": "admin@example.com",
})
// Certificate with DNS validation
cert := certmanager.CreateCertificate("api-tls", "default")
certmanager.SetCertificateIssuer(cert, cmmeta.IssuerReference{
Name: "letsencrypt",
Kind: "ClusterIssuer",
})
certmanager.AddCertificateDNSName(cert, "api.example.com")Input Validation
All user inputs undergo strict validation:
func CreateResource(name, namespace string) (*Resource, error) {
// Validate Kubernetes naming conventions
if !isValidKubernetesName(name) {
return nil, errors.NewValidationError("name", name, "Resource",
[]string{"lowercase", "alphanumeric", "hyphens-only"})
}
// Validate namespace format
if namespace != "" && !isValidNamespace(namespace) {
return nil, errors.NewValidationError("namespace", namespace, "Resource",
[]string{"valid-namespace-name"})
}
return &Resource{Name: name, Namespace: namespace}, nil
}Testing Architecture
Test Organization
Kure maintains 105 test files with comprehensive coverage:
internal/
├── kubernetes/
│ ├── deployment_test.go
│ ├── service_test.go
│ └── ...
├── fluxcd/
│ ├── kustomize_test.go
│ ├── source_test.go
│ └── ...
└── ...
pkg/
├── stack/
│ ├── application_test.go
│ ├── bundle_test.go
│ └── ...
└── ...Testing Patterns
Constructor Testing
func TestCreateDeployment(t *testing.T) {
deployment := CreateDeployment("test-app", "default")
// Validate non-nil result
if deployment == nil {
t.Fatal("expected non-nil deployment")
}
// Validate required fields
if deployment.Name != "test-app" {
t.Errorf("expected name 'test-app', got %s", deployment.Name)
}
if deployment.Namespace != "default" {
t.Errorf("expected namespace 'default', got %s", deployment.Namespace)
}
// Validate default values
if deployment.Spec.Replicas == nil || *deployment.Spec.Replicas != 1 {
t.Error("expected default replicas to be 1")
}
}Helper Function Testing
func TestAddDeploymentContainer(t *testing.T) {
deployment := CreateDeployment("test-app", "default")
container := &corev1.Container{
Name: "main",
Image: "nginx:latest",
}
// Test successful addition
err := AddDeploymentContainer(deployment, container)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Validate container was added
if len(deployment.Spec.Template.Spec.Containers) != 1 {
t.Errorf("expected 1 container, got %d", len(deployment.Spec.Template.Spec.Containers))
}
// Test error conditions
err = AddDeploymentContainer(nil, container)
if err == nil {
t.Error("expected error for nil deployment")
}
err = AddDeploymentContainer(deployment, nil)
if err == nil {
t.Error("expected error for nil container")
}
}Workflow Testing
func TestFluxWorkflowGeneration(t *testing.T) {
// Create test cluster
app := &stack.Application{
Name: "test-app",
Resources: []client.Object{
kubernetes.CreateDeployment("app", "default"),
},
}
bundle := &stack.Bundle{
Name: "test-bundle",
Applications: []*stack.Application{app},
}
node := &stack.Node{
Name: "test-node",
Bundle: bundle,
}
cluster := stack.NewCluster("test-cluster", node)
// Test resource generation
engine := fluxcd.Engine()
resources, err := engine.GenerateFromCluster(cluster)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(resources) == 0 {
t.Error("expected generated resources")
}
// Validate resource types
hasKustomization := false
for _, resource := range resources {
if resource.GetObjectKind().GroupVersionKind().Kind == "Kustomization" {
hasKustomization = true
break
}
}
if !hasKustomization {
t.Error("expected Kustomization resource")
}
}Error Testing
func TestValidationErrors(t *testing.T) {
// Test validation error structure
err := validation.NewValidator().ValidateDeployment(nil)
if err == nil {
t.Fatal("expected validation error")
}
// Test KureError interface
kureErr := errors.GetKureError(err)
if kureErr == nil {
t.Fatal("expected KureError")
}
// Validate error properties
if kureErr.Type() != errors.ErrorTypeValidation {
t.Errorf("expected validation error type, got %s", kureErr.Type())
}
suggestion := kureErr.Suggestion()
if suggestion == "" {
t.Error("expected non-empty suggestion")
}
context := kureErr.Context()
if context == nil {
t.Error("expected error context")
}
}Test Utilities
Common test utilities for consistent testing:
// Test helper functions
func createTestCluster(name string) *stack.Cluster {
// Standard test cluster creation
}
func validateResource(t *testing.T, resource client.Object, expectedKind string) {
// Standard resource validation
}
func assertNoError(t *testing.T, err error) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertError(t *testing.T, err error, expectedType errors.ErrorType) {
if err == nil {
t.Fatal("expected error")
}
if !errors.IsType(err, expectedType) {
t.Errorf("expected error type %s, got %T", expectedType, err)
}
}Appendices
Appendix A: Glossary
Application: Individual Kubernetes workload or resource collection within a Bundle.
Bundle: Deployment unit typically corresponding to a single GitOps resource (e.g., Flux Kustomization).
Cluster: Root abstraction representing a complete Kubernetes cluster configuration.
Domain Model: The hierarchical structure (Cluster → Node → Bundle → Application) representing cluster organization.
GitOps Engine: Implementation of GitOps-specific resource generation and layout integration.
KureError: Structured error type providing contextual information and suggestions.
Layout: Directory structure and manifest organization for GitOps repositories.
Node: Hierarchical container for organizing related Bundles (e.g., infrastructure vs applications).
Patch: Declarative modification of Kubernetes resources using JSONPath-based operations.
Resource Builder: Strongly-typed factory function for creating Kubernetes resources.
Workflow Engine: Complete GitOps workflow implementation combining resource generation, layout integration, and bootstrap capabilities.
Appendix B: References
- Kubernetes API Reference
- Flux Documentation
- ArgoCD Documentation
- cert-manager Documentation
- MetalLB Documentation
- External Secrets Operator
Appendix C: Design Documents
Additional design documentation available in the repository:
pkg/stack/layout/README.md: Layout system overviewpkg/stack/workflow.go: Workflow interface definitions
Appendix D: Migration Guide
For migrating from previous versions:
V1 to V2 Migration
Domain Model Changes:
- Node hierarchy now uses
ParentPathstrings instead of direct parent pointers - Call
InitializePathMap()on root nodes after construction - Bundle hierarchy follows same pattern
Workflow Interface Changes:
- Split monolithic workflow interfaces into specialized components
- Update implementations to use
ResourceGenerator,LayoutIntegrator,BootstrapGenerator - Compose
WorkflowEnginefrom specialized generators
Error Handling Changes:
- Replace generic errors with typed
KureErrorinstances - Internal builder functions use void returns (no nil-checking)
- Handle error context and suggestions in error reporting
Function Naming Changes:
- Constructor functions now follow
New*vsCreate*patterns consistently - Update imports to use new package structure
- Helper function signatures remain compatible
This comprehensive architecture serves as the foundation for Kure’s continued evolution while maintaining backward compatibility and extensibility.