⚠️ Work in Progress
You are viewing development documentation built from the latest commit on main. APIs and features are subject to change.

Generating Flux Manifests

Generating Flux Manifests

This guide walks through the complete workflow for generating a GitOps repository structure with Flux resources using Kure.

Overview

The workflow has four stages:

  1. Define the cluster topology using the domain model
  2. Select the Flux workflow engine
  3. Generate Flux resources and directory layout
  4. Write manifests to disk

Step 1: Define the Cluster

Use the fluent builder to define your cluster’s structure:

import "github.com/go-kure/kure/pkg/stack"

cluster := stack.NewClusterBuilder("production").
    WithNode("infrastructure").
        WithBundle("cert-manager").
            WithApplication("cert-manager", certManagerConfig).
        End().
    End().
    WithNode("applications").
        WithBundle("web-tier").
            WithApplication("frontend", frontendConfig).
            WithApplication("api-gateway", apiConfig).
        End().
    End().
    Build()

Each bundle becomes a Flux Kustomization, and each application generates its Kubernetes manifests.

Step 2: Create the Flux Engine

import (
    "github.com/go-kure/kure/pkg/stack/fluxcd"
    "github.com/go-kure/kure/pkg/stack/layout"
)

engine := fluxcd.EngineWithConfig(
    layout.KustomizationExplicit,  // List files in kustomization.yaml
)

Placement is configured per call on layout.LayoutRules.FluxPlacement (see Step 3 below). FluxUnset normalizes to FluxSeparate. See the Flux Engine reference for configuration options.

Step 3: Generate Resources with Layout

// Define layout rules
rules := layout.LayoutRules{
    NodeGrouping:        layout.GroupByName,
    BundleGrouping:      layout.GroupByName,
    ApplicationGrouping: layout.GroupByName,
    FilePer:             layout.FilePerResource,
    FluxPlacement:       layout.FluxSeparate, // Flux resources in separate tree
}

// Generate layout with Flux resources integrated
ml, err := engine.CreateLayoutWithResources(cluster, rules)
if err != nil {
    return errors.Wrap(err, "failed to create layout")
}

Step 4: Write to Disk

err := layout.WriteManifest(ml, "./clusters")

This produces a directory structure like:

clusters/
  production/
    infrastructure/
      cert-manager/
        cert-manager/
          deployment.yaml
          service.yaml
          kustomization.yaml
        kustomization.yaml        # Flux Kustomization
    applications/
      web-tier/
        frontend/
          deployment.yaml
          service.yaml
        api-gateway/
          deployment.yaml
          service.yaml
        kustomization.yaml        # Flux Kustomization

Layout Configuration

The Layout Engine supports multiple grouping and file organization strategies:

OptionValuesEffect
NodeGroupingGroupByName, GroupFlatCreate subdirectories per node or flatten
BundleGroupingGroupByName, GroupFlatCreate subdirectories per bundle or flatten
ApplicationGroupingGroupByName, GroupFlatCreate subdirectories per app or flatten
FilePerFilePerResource, FilePerKindOne file per resource or group by kind
FluxPlacementFluxSeparate, FluxIntegratedPerLayout, FluxIntegratedPerBundleSeparate dir; a Flux CR per layout node; or Flux CRs at bundle boundaries with children as directories

Umbrella Bundles — Readiness Aggregation

A bundle with non-empty Children becomes an umbrella: Flux will only mark its Kustomization Ready once every child Kustomization is Ready. The Flux engine enforces this by:

  • Forcing spec.wait: true on the umbrella’s Kustomization
  • Prepending an auto spec.healthChecks entry for each direct child

The resulting umbrella Kustomization aggregates child readiness regardless of how many children there are, giving external consumers a single stable anchor:

umbrella := &stack.Bundle{
    Name: "platform",
    Children: []*stack.Bundle{
        {Name: "platform-infra"},
        {Name: "platform-services"},
        {Name: "platform-apps"},
    },
}
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: platform
  namespace: flux-system
spec:
  wait: true
  healthChecks:
  - apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    name: platform-infra
    namespace: flux-system
  - apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    name: platform-services
    namespace: flux-system
  - apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    name: platform-apps
    namespace: flux-system
  # ...rest of spec

User-supplied HealthChecks on the umbrella bundle are appended AFTER the auto entries. Setting Wait: false on a bundle that has Children is rejected at validation time.

Umbrella children must be standalone — a bundle cannot simultaneously be the Bundle of a stack.Node and appear in another bundle’s Children. stack.ValidateCluster rejects any such overlap before resource generation.

Disk layout

In FluxIntegratedPerLayout placement, the umbrella children’s Flux Kustomization CRs live alongside the parent’s, and the parent’s kustomization.yaml references each child via the CR file (not the child subdirectory):

clusters/production/apps/
  platform/                                       # umbrella bundle directory
    flux-system-kustomization-platform.yaml       # umbrella self CR (wait+HC)
    flux-system-kustomization-platform-infra.yaml # child CR (placed at parent)
    flux-system-kustomization-platform-apps.yaml  # child CR (placed at parent)
    kustomization.yaml                            # references the CR files
    platform-infra/                               # umbrella child subdirectory
      workload-*.yaml
      kustomization.yaml                          # workloads only, no Flux CRs
    platform-apps/
      workload-*.yaml
      kustomization.yaml

The child subdirectories contain only their workload manifests and a per-directory kustomization.yaml listing those workloads. They do not contain any flux-system-kustomization-*.yaml files — those live in the parent directory, so Flux applies them once via the parent’s Kustomization.

In FluxSeparate placement, all Kustomization CRs (the umbrella’s own plus every descendant) are written to the shared flux-system/ directory as a flat list.

Augmenter-Added Child Layouts

A LayoutAugmenter can attach sub-ManifestLayout children to a per-app layout after resource generation. In FluxIntegratedPerLayout mode kure automatically emits a Flux Kustomization CR for each eligible child.

Eligibility

A child layout receives a CR when:

  • !child.UmbrellaChild
  • child.ApplicationFileMode != AppFileSingle
  • The ancestor bundle’s SourceRef has both Kind and Name set (nil, empty struct, or missing either field is a hard error — a Kustomization without spec.sourceRef is invalid)

CreateLayoutWithResources validates SourceRef completeness for all bundles before layout walking. Both the node bundle and every umbrella child bundle must have SourceRef.Kind and SourceRef.Name set when either inline mode (FluxIntegratedPerLayout or FluxIntegratedPerBundle) is active — both emit bundle/node CRs carrying a spec.sourceRef. FluxSeparate and non-Flux callers are unaffected.

Ordered reconciliation with DependsOn

Set ManifestLayout.DependsOn to a list of sibling layout names to express reconciliation order between hook groups. The integrator translates these into spec.dependsOn entries on the emitted CR:

preInstall := &layout.ManifestLayout{
    Name: "nginx-00-pre-install",
    // ...
}
hooks := &layout.ManifestLayout{
    Name:      "nginx-01-hooks",
    DependsOn: []string{"nginx-00-pre-install"},
    // ...
}

This produces a nginx-01-hooks Kustomization CR with:

spec:
  dependsOn:
    - name: nginx-00-pre-install

Flux reconciles nginx-01-hooks only after nginx-00-pre-install is healthy.

Naming uniqueness

The child layout Name becomes the Flux Kustomization CR’s metadata.name. Since all Kustomization CRs live in the flux-system namespace, names must be globally unique across the cluster. The recommended convention is {appName}-{hookGroupDir} (e.g. nginx-00-pre-install, nginx-01-hooks). Augmenters are responsible for enforcing this uniqueness.

Disk layout

clusters/production/prod/
  flux-system-kustomization-nginx.yaml      # app CR (placed at node level)
  kustomization.yaml                        # references nginx CR
  nginx/
    flux-system-kustomization-nginx-00-pre-install.yaml
    flux-system-kustomization-nginx-01-hooks.yaml
    kustomization.yaml                      # references hook CRs
    nginx-00-pre-install/
      workload-*.yaml
      kustomization.yaml
    nginx-01-hooks/
      workload-*.yaml
      kustomization.yaml

Bootstrap

Generate Flux system bootstrap manifests. Two modes are available:

  • "flux-operator" (default) — emits a full Flux Operator install bundle (CRDs, Deployment, RBAC). Recommended for new clusters.
  • "gotk" — emits the legacy GitOps Toolkit component manifests directly.

When FluxMode is empty, it defaults to "flux-operator".

bootstrapConfig := &stack.BootstrapConfig{
    Enabled:     true,
    FluxMode:    "flux-operator", // or "gotk"; empty defaults to "flux-operator"
    FluxVersion: "v2.8.2",
    SourceRef:   sourceRef,
}

objects, err := engine.GenerateBootstrap(bootstrapConfig, rootNode)

Further Reading