Documentation
¶
Overview ¶
Package stack provides the core domain model for defining and generating Kubernetes cluster configurations with GitOps tooling (Flux CD or ArgoCD).
Overview ¶
The stack package models a Kubernetes cluster as a hierarchical tree of nodes, where each node can contain bundles of applications. This structure maps directly to the directory layouts expected by GitOps tools, enabling declarative generation of the complete repository structure needed for Flux Kustomizations or ArgoCD Applications.
Domain Model ¶
The core types form a hierarchical structure:
Cluster └── Node (tree structure) ├── Bundle │ └── Applications └── Children (nested Nodes) - [Cluster]: Top-level configuration including GitOps settings - [Node]: Hierarchical structure for organizing deployment units - [Bundle]: Collection of applications deployed together - [Application]: Individual Kubernetes workload or component
Fluent Builder API ¶
The package provides a fluent builder API for constructing cluster configurations in a type-safe, readable manner:
cluster, err := stack.NewClusterBuilder("production").
WithGitOps(&stack.GitOpsConfig{Type: "flux"}).
WithNode("infrastructure").
WithBundle("monitoring").
WithApplication("prometheus", prometheusConfig).
End().
End().
Build()
The fluent API uses a copy-on-write pattern where each method returns a new builder instance, allowing safe branching and concurrent construction. Build() returns (*Cluster, error) to surface any validation errors.
Workflow Integration ¶
The Workflow interface abstracts the generation of GitOps-specific resources. Implementations exist for both Flux CD and ArgoCD:
- github.com/go-kure/kure/pkg/stack/fluxcd.WorkflowEngine: Generates Flux Kustomizations, GitRepositories, and related resources
- github.com/go-kure/kure/pkg/stack/argocd.WorkflowEngine: Generates ArgoCD Applications and AppProjects
Use the workflow to generate all manifests for a cluster:
// _ "github.com/go-kure/kure/pkg/stack/fluxcd" must be imported to register the provider
wf, err := stack.NewWorkflow("flux")
Layout Generation ¶
The github.com/go-kure/kure/pkg/stack/layout subpackage handles writing generated manifests to disk. Use Workflow.CreateLayoutWithResources to produce a ManifestLayoutResult, then call WriteToDisk to write all files:
ml, _ := wf.CreateLayoutWithResources(cluster, layout.LayoutRules{})
_ = ml.WriteToDisk("./clusters/prod")
The github.com/go-kure/kure/pkg/stack/layout subpackage also exposes lower-level layout primitives following the conventions expected by GitOps tools.
Package References ¶
Nodes can specify a [PackageRef] to indicate that a subtree should be packaged as a separate OCI artifact or kurel package. When undefined, the PackageRef is inherited from the parent node.
Example ¶
Complete example creating a cluster with infrastructure and applications:
// Define the cluster structure
cluster, err := stack.NewClusterBuilder("prod-cluster").
WithGitOps(&stack.GitOpsConfig{
Type: "flux",
Bootstrap: &stack.BootstrapConfig{
Enabled: true,
FluxMode: "flux-operator",
},
}).
WithNode("infrastructure").
WithBundle("cert-manager").
WithApplication("cert-manager", certManagerConfig).
End().
WithChild("applications").
WithBundle("web-app").
WithApplication("frontend", frontendConfig).
WithApplication("backend", backendConfig).
End().
End().
End().
Build()
// Generate Flux manifests and write to disk
// (requires: _ "github.com/go-kure/kure/pkg/stack/fluxcd" side-effect import)
wf, _ := stack.NewWorkflow("flux")
ml, _ := wf.CreateLayoutWithResources(cluster, layout.LayoutRules{})
_ = ml.WriteToDisk("./clusters/prod")
Dual Access Pattern (Exported Fields and Getter/Setter Methods) ¶
Several types in this package, notably Cluster and Node, expose their data both as exported struct fields and through getter/setter methods. The methods are intentionally thin wrappers without additional validation, meaning both access paths are functionally equivalent.
This design serves two audiences:
- Internal and test code benefits from direct field access, which is concise and idiomatic in Go.
- External library consumers can use getter/setter methods to decouple from the concrete field layout, making it easier to introduce validation or indirection in a future version without breaking callers.
When writing new code inside the kure repository, prefer direct field access. When consuming the stack package as a library, prefer the getter/setter methods. See the Cluster type documentation for details.
Example (ArchitectureValidate) ¶
package main
import (
"fmt"
"github.com/go-kure/kure/pkg/errors"
"github.com/go-kure/kure/pkg/stack"
)
func main() {
c := stack.NewCluster("prod", &stack.Node{
Name: "apps",
Bundle: &stack.Bundle{Name: "web", Applications: []*stack.Application{nil}},
})
check := func(c *stack.Cluster) error {
// Validation is a call the caller makes, not a side effect of construction.
if err := stack.ValidateCluster(c); err != nil {
return errors.Wrap(err, "cluster is not layoutable")
}
return nil
}
fmt.Println(check(c))
}
Output: cluster is not layoutable: bundle "web" failed validation: validation failed for Bundle 'web' field 'applications': application at index 0 is nil
Example (DomainModel) ¶
package main
import (
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/go-kure/kure/pkg/kubernetes"
"github.com/go-kure/kure/pkg/stack"
"github.com/go-kure/kure/pkg/stack/layout"
)
// The tree example of the "Domain Model" concept page
// (site/content/concepts/domain-model.md) is generated from this function
// (scripts/gen-doc-examples.sh); the page's fluent-builder block is
// ExampleNewClusterBuilder.
// workload is the stack.ApplicationConfig the page's applications use: one
// ConfigMap named after the application.
type workload struct{}
func (workload) Generate(app *stack.Application) ([]*client.Object, error) {
var obj client.Object = kubernetes.CreateConfigMap(app.Name, app.Namespace)
return []*client.Object{&obj}, nil
}
var (
certManagerConfig stack.ApplicationConfig = workload{}
prometheusConfig stack.ApplicationConfig = workload{}
frontendConfig stack.ApplicationConfig = workload{}
apiConfig stack.ApplicationConfig = workload{}
)
// printLayout prints the repository path of every directory in the layout.
func printLayout(ml *layout.ManifestLayout) {
fmt.Println(ml.FullRepoPath())
for _, child := range ml.Children {
printLayout(child)
}
}
func main() {
cluster := stack.NewCluster("production", &stack.Node{
Name: "production",
Children: []*stack.Node{
{Name: "infrastructure", Children: []*stack.Node{
{Name: "cert-manager", Bundle: &stack.Bundle{Name: "cert-manager", Applications: []*stack.Application{
stack.NewApplication("cert-manager", "cert-manager", certManagerConfig),
}}},
{Name: "monitoring", Bundle: &stack.Bundle{Name: "monitoring", Applications: []*stack.Application{
stack.NewApplication("prometheus", "monitoring", prometheusConfig),
}}},
}},
{Name: "applications", Bundle: &stack.Bundle{Name: "web-apps", Applications: []*stack.Application{
stack.NewApplication("frontend", "web", frontendConfig),
stack.NewApplication("api", "web", apiConfig),
}}},
},
})
rules := layout.DefaultLayoutRules()
rules.BundleGrouping = layout.GroupByName
rules.ApplicationGrouping = layout.GroupByName
ml, err := layout.WalkCluster(cluster, rules)
if err != nil {
panic(err)
}
printLayout(ml)
}
Output: production production/infrastructure production/infrastructure/cert-manager production/infrastructure/cert-manager/cert-manager production/infrastructure/cert-manager/cert-manager/cert-manager production/infrastructure/monitoring production/infrastructure/monitoring/monitoring production/infrastructure/monitoring/monitoring/prometheus production/applications production/applications/web-apps production/applications/web-apps/frontend production/applications/web-apps/api
Index ¶
- Constants
- func RegisterArgoWorkflow(factory func() Workflow)
- func RegisterFluxWorkflow(factory func() Workflow)
- func ValidateCluster(c *Cluster) error
- type Application
- type ApplicationConfig
- type BootstrapConfig
- type Bundle
- func (a *Bundle) Generate() ([]*client.Object, error)
- func (b *Bundle) GetParent() *Bundle
- func (b *Bundle) GetParentPath() string
- func (b *Bundle) GetPath() string
- func (b *Bundle) InitializePathMap(allBundles []*Bundle)
- func (a *Bundle) InitializeUmbrella()
- func (a *Bundle) IsUmbrella() bool
- func (b *Bundle) SetParent(parent *Bundle)
- func (a *Bundle) Validate() error
- type BundleBuilder
- type Cluster
- type ClusterBuilder
- type GitOpsConfig
- type HealthCheck
- type LayoutRulesProvider
- type ManifestLayoutResult
- type Node
- func (n *Node) GetBundle() *Bundle
- func (n *Node) GetChildren() []*Node
- func (n *Node) GetName() string
- func (n *Node) GetPackageRef() *schema.GroupVersionKind
- func (n *Node) GetParent() *Node
- func (n *Node) GetParentPath() string
- func (n *Node) GetPath() string
- func (n *Node) InitializePathMap()
- func (n *Node) SetBundle(bundle *Bundle)
- func (n *Node) SetChildren(children []*Node)
- func (n *Node) SetName(name string)
- func (n *Node) SetPackageRef(ref *schema.GroupVersionKind)
- func (n *Node) SetParent(parent *Node)
- func (n *Node) SetParentPath(path string)
- type NodeBuilder
- type Patch
- type PatchSelector
- type PostBuild
- type SourceRef
- type SubstituteRef
- type Validator
- type Workflow
Examples ¶
Constants ¶
const ( // AnnotationFluxPruneKey is the Flux kustomize-controller annotation key // used to control pruning behavior on individual resources. AnnotationFluxPruneKey = "kustomize.toolkit.fluxcd.io/prune" // AnnotationFluxPruneDisabled is the value that prevents a resource from // being pruned during Flux garbage collection. AnnotationFluxPruneDisabled = "disabled" )
Variables ¶
This section is empty.
Functions ¶
func RegisterArgoWorkflow ¶
func RegisterArgoWorkflow(factory func() Workflow)
RegisterArgoWorkflow registers the ArgoCD workflow factory. This is called by the argocd package during init.
func RegisterFluxWorkflow ¶
func RegisterFluxWorkflow(factory func() Workflow)
RegisterFluxWorkflow registers the Flux workflow factory. This is called by the fluxcd package during init.
func ValidateCluster ¶
ValidateCluster performs cluster-level structural validation that cannot be expressed on a single Bundle alone. It is the single validation entry point shared by the resource generator, layout walker, layout integrator, and the v1alpha1 converter round-trip.
It enforces:
- The Node tree has no cycle: a Node reached again from one of its own descendants is rejected, naming the node where the cycle closes.
- Every Node bundle passes Bundle.Validate (which recursively validates umbrella Children subtrees including cycle detection).
- Disjointness: a bundle pointer appearing inside any umbrella Children subtree must NOT also be attached as the Bundle of any stack.Node.
- No umbrella child pointer is shared by two distinct umbrella parents.
- Multi-package rejection: if any Node has a PackageRef set and any bundle in the cluster has umbrella Children, the cluster is rejected. Cross-package umbrella semantics are follow-up work.
ValidateCluster is safe to call with a nil cluster or a cluster with no root node (it returns nil in both cases).
Types ¶
type Application ¶
type Application struct {
Name string
Namespace string
Config ApplicationConfig
}
Application represents a deployable application with a configuration.
func NewApplication ¶
func NewApplication(name, namespace string, cfg ApplicationConfig) *Application
NewApplication constructs an Application with the provided parameters.
func (*Application) Generate ¶
func (a *Application) Generate() ([]*client.Object, error)
Generate returns the resources for this application. If the Config implements the Validator interface, Validate() is called before Generate(). A validation error stops generation immediately.
Example ¶
package main
import (
"fmt"
"strconv"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/go-kure/kure/pkg/errors"
"github.com/go-kure/kure/pkg/kubernetes"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
// myConfig is the ApplicationConfig the README's "Optional Validation"
// section declares; the other examples use it too.
type myConfig struct{ Port int }
func (c *myConfig) Validate() error {
if c.Port <= 0 {
return errors.New("port must be positive")
}
return nil
}
func (c *myConfig) Generate(app *stack.Application) ([]*client.Object, error) {
cm := kubernetes.CreateConfigMap(app.Name, app.Namespace)
cm.Data = map[string]string{"port": strconv.Itoa(c.Port)}
var obj client.Object = cm
return []*client.Object{&obj}, nil
}
func main() {
prometheusConfig := &myConfig{Port: 9090}
app := stack.NewApplication("prometheus", "monitoring", prometheusConfig)
resources, err := app.Generate()
if err != nil {
panic(err)
}
fmt.Println(len(resources), (*resources[0]).GetName())
}
Output: 1 prometheus
func (*Application) SetConfig ¶
func (a *Application) SetConfig(cfg ApplicationConfig)
SetConfig replaces the application configuration.
func (*Application) SetName ¶
func (a *Application) SetName(name string)
SetName updates the application name.
func (*Application) SetNamespace ¶
func (a *Application) SetNamespace(ns string)
SetNamespace updates the target namespace.
type ApplicationConfig ¶
type ApplicationConfig interface {
Generate(*Application) ([]*client.Object, error)
}
ApplicationConfig describes the behaviour of specific application types.
type BootstrapConfig ¶
type BootstrapConfig struct {
// Common fields
Enabled bool `yaml:"enabled"`
// Flux-specific
FluxMode string `yaml:"fluxMode,omitempty"` // "flux-operator" (default) or "gotk" (legacy)
// FluxVersion selects the Flux release. In gotk mode, empty or the vendored
// fluxcd.GotkVersion builds offline from the bundle kure ships; any other
// value downloads manifests at generation time: the named release for a
// "vX.Y.Z" value, and the latest release for anything else ("latest", or a
// version without its "v"), as upstream flux2 resolves it. In
// flux-operator mode it is the FluxInstance
// distribution version.
FluxVersion string `yaml:"fluxVersion,omitempty"`
Components []string `yaml:"components,omitempty"`
Registry string `yaml:"registry,omitempty"`
ImagePullSecret string `yaml:"imagePullSecret,omitempty"`
// Source configuration
SourceKind string `yaml:"sourceKind,omitempty"` // "GitRepository" or "OCIRepository"
SourceURL string `yaml:"sourceURL,omitempty"` // OCI/Git repository URL
SourceRef string `yaml:"sourceRef,omitempty"` // Tag/branch/ref
// SyncName names the source and Kustomization that the FluxInstance sync
// creates (spec.sync.name). flux-operator mode only: gotk mode ignores it.
// It has no effect without SourceURL, because no sync block is emitted
// then. Empty leaves the name to the operator, which defaults it to the
// FluxInstance's namespace. The CRD caps it at 63 characters and makes it
// immutable once set, so changing it means recreating the FluxInstance.
SyncName string `yaml:"syncName,omitempty"`
// Prune controls garbage collection on the bootstrap Kustomization.
// Unset emits prune: false: the upstream field is required with no
// omitempty, so it cannot be left out of the YAML, and an unset input is
// not a request for destructive garbage collection.
Prune *bool `yaml:"prune,omitempty"`
// ArgoCD-specific (mock for now)
ArgoCDVersion string `yaml:"argoCDVersion,omitempty"`
ArgoCDNamespace string `yaml:"argoCDNamespace,omitempty"`
}
BootstrapConfig defines the bootstrap configuration for GitOps tools
type Bundle ¶
type Bundle struct {
// Name identifies the application set.
Name string
// ParentPath is the hierarchical path to the parent bundle (e.g., "cluster/infrastructure")
// Empty for root bundles. This avoids circular references while maintaining hierarchy.
ParentPath string
// DependsOn lists other bundles this bundle depends on
DependsOn []*Bundle
// NamedDependsOn lists names of kustomizations this bundle depends on, by name.
// Unlike DependsOn, names need not resolve to in-scope Bundle objects.
// Both fields are merged into Kustomization.Spec.DependsOn.
NamedDependsOn []string
// Children holds bundles whose Flux Kustomization CRs are rendered into
// this bundle's tar path and whose readiness is aggregated into this
// bundle's HealthChecks. When non-empty, this bundle acts as an umbrella:
// it is Ready only when all Children are Ready. Children bundles must be
// standalone — they cannot simultaneously be the Bundle of a stack.Node.
Children []*Bundle
// Interval controls how often Flux reconciles the bundle. It must parse as
// a Go duration (e.g. "10m"); empty means the generator's default.
// Validate rejects any other value.
Interval string
// SourceRef specifies the source for the bundle.
SourceRef *SourceRef
// Applications holds the Kubernetes objects that belong to the application.
Applications []*Application
// Labels are common labels that should be applied to each resource.
Labels map[string]string
// Annotations are common annotations propagated to all generated resources and
// the generated Kustomization resource. Application-specific annotations take precedence.
Annotations map[string]string
// Description provides a human-readable description of the bundle.
Description string
// Prune enables garbage collection of resources removed from the bundle.
Prune *bool
// Wait causes the Kustomization to wait for resources to become ready.
Wait *bool
// Timeout is the maximum duration to wait for resources to be ready (e.g. "5m").
// It must parse as a Go duration; empty leaves the field unset.
Timeout string
// RetryInterval is the interval between retry attempts for failed reconciliations (e.g. "2m").
// It must parse as a Go duration; empty leaves the field unset.
RetryInterval string
// Force causes Flux to re-apply resources even if there are no detected changes.
Force *bool
// Suspend disables reconciliation when true. Set to false to resume.
Suspend *bool
// HealthChecks lists resources whose health is monitored during reconciliation.
// When specified, the Kustomization waits for these resources to become ready.
HealthChecks []HealthCheck
// Patches lists strategic merge or JSON patches to apply to resources after
// kustomize build. Each patch targets resources matching its selector.
Patches []Patch
// PostBuild configures variable substitution performed after kustomize build.
PostBuild *PostBuild
// contains filtered or unexported fields
}
Bundle represents a unit of deployment, typically the resources that are reconciled by a single Flux Kustomization.
func NewBundle ¶
NewBundle constructs a Bundle with the given name, resources and labels. It returns an error if validation fails.
Example ¶
package main
import (
"fmt"
"strconv"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/go-kure/kure/pkg/errors"
"github.com/go-kure/kure/pkg/kubernetes"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
// myConfig is the ApplicationConfig the README's "Optional Validation"
// section declares; the other examples use it too.
type myConfig struct{ Port int }
func (c *myConfig) Validate() error {
if c.Port <= 0 {
return errors.New("port must be positive")
}
return nil
}
func (c *myConfig) Generate(app *stack.Application) ([]*client.Object, error) {
cm := kubernetes.CreateConfigMap(app.Name, app.Namespace)
cm.Data = map[string]string{"port": strconv.Itoa(c.Port)}
var obj client.Object = cm
return []*client.Object{&obj}, nil
}
func main() {
apps := []*stack.Application{
stack.NewApplication("prometheus", "monitoring", &myConfig{Port: 9090}),
}
labels := map[string]string{"team": "platform"}
certManagerBundle := &stack.Bundle{Name: "cert-manager"}
bundle, err := stack.NewBundle("monitoring", apps, labels)
if err != nil {
panic(err)
}
// Pointer-based — when you hold the bundle object:
bundle.DependsOn = []*stack.Bundle{certManagerBundle}
// Name-based — when you only know the name (e.g. a hook-phase bundle):
bundle.NamedDependsOn = []string{"cert-manager-pre-install"}
bundle.Interval = "10m"
if err := bundle.Validate(); err != nil {
panic(err)
}
fmt.Println(bundle.Name, bundle.DependsOn[0].Name, bundle.NamedDependsOn, bundle.Interval)
}
Output: monitoring cert-manager [cert-manager-pre-install] 10m
func (*Bundle) GetParentPath ¶
GetParentPath returns the hierarchical path to the parent bundle.
func (*Bundle) InitializePathMap ¶
InitializePathMap builds the runtime path lookup map for efficient hierarchy navigation. This should be called on the root bundle after the tree structure is complete.
func (*Bundle) InitializeUmbrella ¶
func (a *Bundle) InitializeUmbrella()
InitializeUmbrella walks the umbrella Children subtree and sets each child's runtime parent pointer (via SetParent) so that code can walk upward from any child (GetParent). Idempotent and safe to call multiple times.
func (*Bundle) IsUmbrella ¶
IsUmbrella reports whether the bundle acts as an umbrella (has Children that contribute their Flux Kustomizations into this bundle's directory).
type BundleBuilder ¶
type BundleBuilder interface {
WithApplication(name string, appConfig ApplicationConfig) BundleBuilder
WithDependency(bundle *Bundle) BundleBuilder
WithSourceRef(sourceRef *SourceRef) BundleBuilder
End() NodeBuilder
Build() (*Cluster, error)
}
BundleBuilder provides fluent interface for building Bundle configurations.
type Cluster ¶
type Cluster struct {
Name string `yaml:"name"`
Node *Node `yaml:"node,omitempty"`
GitOps *GitOpsConfig `yaml:"gitops,omitempty"`
}
Cluster describes a cluster configuration. A cluster configuration is a set of configurations that are packaged in one or more package units.
Dual Access Pattern ¶
Cluster exposes its fields (Name, Node, GitOps) as exported struct fields and also provides getter/setter methods (GetName/SetName, GetNode/SetNode, GetGitOps/SetGitOps). The getters and setters are thin wrappers that do not add validation; both access paths read and write the same underlying fields.
This dual access pattern exists intentionally:
- Exported fields allow direct, concise access that is idiomatic in Go, particularly useful in tests, internal code, and YAML serialization/deserialization (struct tags operate on exported fields).
- Getter/setter methods provide an encapsulated API surface for library consumers who may prefer method-based access or who want to program against a future interface without depending on concrete field layout.
Guidance for New Code ¶
Within the kure codebase (tests, internal packages, CLI commands), prefer direct field access for brevity:
c.Name = "prod" fmt.Println(c.Node)
When writing code that consumes the stack package as an external library, prefer the getter/setter methods so that any future validation or indirection can be introduced without breaking callers:
c.SetName("prod")
node := c.GetNode()
Example ¶
package main
import (
"fmt"
"github.com/go-kure/kure/pkg/stack"
)
func main() {
cluster := &stack.Cluster{
Name: "prod",
Node: &stack.Node{Name: "apps"},
}
fmt.Println(cluster.Name, cluster.Node.Name)
}
Output: prod apps
func NewCluster ¶
NewCluster creates a Cluster with the provided metadata.
Example ¶
package main
import (
"fmt"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
func main() {
rootNode := &stack.Node{Name: "flux-system"}
cluster := stack.NewCluster("production", rootNode)
cluster.SetGitOps(&stack.GitOpsConfig{
Type: "flux",
})
fmt.Println(cluster.GetName(), cluster.GetNode().Name, cluster.GetGitOps().Type)
}
Output: production flux-system flux
func (*Cluster) GetGitOps ¶
func (c *Cluster) GetGitOps() *GitOpsConfig
func (*Cluster) SetGitOps ¶
func (c *Cluster) SetGitOps(g *GitOpsConfig)
type ClusterBuilder ¶
type ClusterBuilder interface {
WithNode(name string) NodeBuilder
WithGitOps(gitops *GitOpsConfig) ClusterBuilder
Build() (*Cluster, error)
}
ClusterBuilder provides fluent interface for building Cluster configurations.
func NewClusterBuilder ¶
func NewClusterBuilder(name string) ClusterBuilder
NewClusterBuilder creates a new fluent cluster builder.
Example ¶
package main
import (
"fmt"
"strconv"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/go-kure/kure/pkg/errors"
"github.com/go-kure/kure/pkg/kubernetes"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
// myConfig is the ApplicationConfig the README's "Optional Validation"
// section declares; the other examples use it too.
type myConfig struct{ Port int }
func (c *myConfig) Validate() error {
if c.Port <= 0 {
return errors.New("port must be positive")
}
return nil
}
func (c *myConfig) Generate(app *stack.Application) ([]*client.Object, error) {
cm := kubernetes.CreateConfigMap(app.Name, app.Namespace)
cm.Data = map[string]string{"port": strconv.Itoa(c.Port)}
var obj client.Object = cm
return []*client.Object{&obj}, nil
}
func main() {
appConfig := &myConfig{Port: 9090}
cluster, err := stack.NewClusterBuilder("production").
WithNode("infrastructure").
WithBundle("monitoring").
WithApplication("prometheus", appConfig).
End().
End().
Build()
if err != nil {
panic(err)
}
fmt.Println(cluster.Name, cluster.Node.Name, cluster.Node.Bundle.Name)
}
Output: production infrastructure monitoring
type GitOpsConfig ¶
type GitOpsConfig struct {
Type string `yaml:"type"` // "flux" or "argocd"
Bootstrap *BootstrapConfig `yaml:"bootstrap,omitempty"`
}
GitOpsConfig defines the GitOps tool configuration for the cluster
type HealthCheck ¶
type HealthCheck struct {
// APIVersion of the resource (e.g. "apps/v1", "helm.toolkit.fluxcd.io/v2").
APIVersion string
// Kind of the resource (e.g. "Deployment", "HelmRelease").
Kind string
// Name of the resource.
Name string
// Namespace of the resource. When empty, defaults to the Kustomization namespace.
Namespace string
}
HealthCheck defines a resource to be monitored for health during reconciliation.
Example ¶
package main
import (
"fmt"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
func main() {
bundle := &stack.Bundle{Name: "web"}
bundle.HealthChecks = []stack.HealthCheck{
{APIVersion: "apps/v1", Kind: "Deployment", Name: "web", Namespace: "default"},
}
fmt.Println(bundle.HealthChecks[0].Kind, bundle.HealthChecks[0].Name)
}
Output: Deployment web
type LayoutRulesProvider ¶
type LayoutRulesProvider interface {
Validate() error
}
LayoutRulesProvider is the interface for layout configuration passed to CreateLayoutWithResources. The concrete implementation is layout.LayoutRules from pkg/stack/layout. Defined here to avoid an import cycle between pkg/stack and pkg/stack/layout.
type ManifestLayoutResult ¶
ManifestLayoutResult is the interface for layout results returned by CreateLayoutWithResources. The concrete implementation is *layout.ManifestLayout from pkg/stack/layout. Callers that need the full concrete type should type-assert: ml, ok := result.(*layout.ManifestLayout).
type Node ¶
type Node struct {
// Name identifies the application set.
Name string `yaml:"name"`
// ParentPath is the hierarchical path to the parent node (e.g., "cluster/infrastructure")
// Empty for root nodes. This avoids circular references while maintaining hierarchy.
ParentPath string `yaml:"parentPath,omitempty"`
// Children list child bundles
Children []*Node `yaml:"children,omitempty"`
// PackageRef identifies in which package the tree of resources get bundled together
// If undefined, the PackageRef of the parent is inherited
PackageRef *schema.GroupVersionKind `yaml:"packageref,omitempty"`
// Bundle holds the applications that get deployed on this level
Bundle *Bundle `yaml:"bundle,omitempty"`
// contains filtered or unexported fields
}
Node represents a hierarchic structure holding all deployment bundles each tree has a list of children, which can be a deployment, or a subtree It could match a kubernetes cluster's full configuration, or it could be just a part of that, when parts are e.g. packaged in different OCI artifacts Tree's with a common PackageRef are packaged together
Example ¶
package main
import (
"fmt"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
func main() {
childNode := &stack.Node{Name: "cert-manager"}
monitoringBundle := &stack.Bundle{Name: "monitoring"}
node := &stack.Node{
Name: "infrastructure",
Children: []*stack.Node{childNode},
Bundle: monitoringBundle,
}
fmt.Println(node.Name, node.Children[0].Name, node.Bundle.Name)
}
Output: infrastructure cert-manager monitoring
func (*Node) GetChildren ¶
func (*Node) GetPackageRef ¶
func (n *Node) GetPackageRef() *schema.GroupVersionKind
func (*Node) GetParentPath ¶
func (*Node) InitializePathMap ¶
func (n *Node) InitializePathMap()
InitializePathMap builds the runtime path lookup map for efficient hierarchy navigation. This should be called on the root node after the tree structure is complete.
func (*Node) SetChildren ¶
func (*Node) SetPackageRef ¶
func (n *Node) SetPackageRef(ref *schema.GroupVersionKind)
func (*Node) SetParent ¶
SetParent sets the parent node and updates the ParentPath accordingly. This method maintains both the serializable path and runtime reference.
func (*Node) SetParentPath ¶
type NodeBuilder ¶
type NodeBuilder interface {
WithChild(name string) NodeBuilder
WithBundle(name string) BundleBuilder
WithPackageRef(ref *schema.GroupVersionKind) NodeBuilder
End() ClusterBuilder
Build() (*Cluster, error)
}
NodeBuilder provides fluent interface for building Node configurations.
type Patch ¶
type Patch struct {
// Patch is the patch content in strategic merge patch or JSON patch format.
Patch string
// Target selects which resources the patch applies to.
// When nil the patch applies to all resources.
Target *PatchSelector
}
Patch defines a strategic merge or JSON patch applied to resources after kustomize build.
type PatchSelector ¶
type PatchSelector struct {
// Group of the target resource (e.g. "apps").
Group string
// Version of the target resource (e.g. "v1").
Version string
// Kind of the target resource (e.g. "Deployment").
Kind string
// Name of the target resource.
Name string
// Namespace of the target resource.
Namespace string
// LabelSelector is a label selector expression.
LabelSelector string
// AnnotationSelector is an annotation selector expression.
AnnotationSelector string
}
PatchSelector selects Kubernetes resources by GVK and metadata filters.
type PostBuild ¶
type PostBuild struct {
// Substitute contains inline key-value substitution variables.
// Values are substituted for ${VAR} occurrences in manifests.
Substitute map[string]string
// SubstituteFrom lists ConfigMaps and Secrets whose data is merged into
// the substitution variables.
SubstituteFrom []SubstituteRef
}
PostBuild configures variable substitution performed after kustomize build.
type SourceRef ¶
type SourceRef struct {
Kind string
Name string
Namespace string
// URL is the repository URL (OCI or Git). When set, the resource generator
// creates the source CRD in addition to referencing it.
URL string
// Tag is the tag or semver reference for OCI sources.
Tag string
// Branch is the branch reference for Git sources.
Branch string
}
SourceRef defines a reference to a Flux source. When Kind, Name and Namespace are set, the Kustomization will reference an existing source. When URL is also set, the resource generator will create the source CRD.
Example ¶
package main
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
func main() {
// A bundle's SourceRef names the Flux source its Kustomization reads from;
// with URL set, the generator creates that source as well.
bundle := &stack.Bundle{Name: "apps"}
bundle.SourceRef = &stack.SourceRef{
Kind: "OCIRepository",
Name: "my-registry",
Namespace: "flux-system",
URL: "oci://registry.example.com/manifests",
Tag: "v1.0.0",
}
// A node's PackageRef names the package its subtree is bundled into;
// child nodes without one inherit it.
node := &stack.Node{Name: "apps", Bundle: bundle}
node.SetPackageRef(&schema.GroupVersionKind{
Group: "source.toolkit.fluxcd.io",
Version: "v1",
Kind: "OCIRepository",
})
fmt.Println(bundle.SourceRef.URL, node.GetPackageRef().Kind)
}
Output: oci://registry.example.com/manifests OCIRepository
type SubstituteRef ¶
type SubstituteRef struct {
// Kind is ConfigMap or Secret.
Kind string
// Name of the ConfigMap or Secret.
Name string
// Optional allows the reference to be absent without causing an error.
Optional bool
}
SubstituteRef defines a reference to a ConfigMap or Secret used as a source of PostBuild substitution variables.
type Validator ¶
type Validator interface {
Validate() error
}
Validator is an optional interface that ApplicationConfig implementations can implement to validate their configuration before generation. If an ApplicationConfig also implements Validator, Application.Generate() calls Validate() automatically before calling Generate().
type Workflow ¶
type Workflow interface {
// GenerateFromCluster creates GitOps resources from a cluster definition.
// This is the primary entry point for resource generation.
GenerateFromCluster(*Cluster) ([]client.Object, error)
// CreateLayoutWithResources creates a new manifest layout that includes
// both the application manifests and the GitOps resources needed to
// deploy them. This combines manifest generation with GitOps resource
// generation in a single operation.
// The rules parameter must be a layout.LayoutRules value.
// The returned ManifestLayoutResult is a *layout.ManifestLayout.
CreateLayoutWithResources(*Cluster, LayoutRulesProvider) (ManifestLayoutResult, error)
// GenerateBootstrap creates bootstrap resources for initializing the
// GitOps system itself. This is used to set up the GitOps controller
// (Flux, ArgoCD, etc.) in the cluster.
GenerateBootstrap(*BootstrapConfig, *Node) ([]client.Object, error)
}
Workflow defines the core interface for GitOps workflow implementations. This interface provides a minimal abstraction for converting stack definitions into GitOps-specific resources (Flux Kustomizations, ArgoCD Applications, etc.).
func NewWorkflow ¶
NewWorkflow creates a workflow implementation based on the provider type. Supported providers: "flux", "argocd"
Example ¶
package main
import (
"fmt"
"strconv"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/go-kure/kure/pkg/errors"
"github.com/go-kure/kure/pkg/kubernetes"
"github.com/go-kure/kure/pkg/stack"
_ "github.com/go-kure/kure/pkg/stack/fluxcd"
)
// myConfig is the ApplicationConfig the README's "Optional Validation"
// section declares; the other examples use it too.
type myConfig struct{ Port int }
func (c *myConfig) Validate() error {
if c.Port <= 0 {
return errors.New("port must be positive")
}
return nil
}
func (c *myConfig) Generate(app *stack.Application) ([]*client.Object, error) {
cm := kubernetes.CreateConfigMap(app.Name, app.Namespace)
cm.Data = map[string]string{"port": strconv.Itoa(c.Port)}
var obj client.Object = cm
return []*client.Object{&obj}, nil
}
func main() {
cluster, err := stack.NewClusterBuilder("production").
WithNode("infrastructure").
WithBundle("monitoring").
WithApplication("prometheus", &myConfig{Port: 9090}).
End().
End().
Build()
if err != nil {
panic(err)
}
// Create a workflow for your GitOps tool
wf, err := stack.NewWorkflow("flux")
if err != nil {
panic(err)
}
// Generate GitOps resources from the cluster definition
objects, err := wf.GenerateFromCluster(cluster)
if err != nil {
panic(err)
}
for _, obj := range objects {
fmt.Println(obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName())
}
}
Output: Kustomization monitoring
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package helm provides client-side Helm chart rendering from OCI registries and HTTP Helm repositories.
|
Package helm provides client-side Helm chart rendering from OCI registries and HTTP Helm repositories. |
|
Package layout provides utilities for generating cluster directory layouts and for writing Kubernetes and Flux manifests to disk.
|
Package layout provides utilities for generating cluster directory layouts and for writing Kubernetes and Flux manifests to disk. |