Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

The Policy Generator is a Kustomize exec plugin that constructs Open Cluster Management policies from Kubernetes YAML files. It's a Go binary that processes PolicyGenerator Custom Resources and outputs Policy, Placement, and PlacementBinding manifests.

## Build Commands

```bash
# Build the plugin (installs to ~/.config/kustomize/plugin/...)
make build

# Build binary in repository root (for standalone use)
make build-binary

# Run all tests
make test

# Run tests with coverage
make test-coverage

# Run single test
go test -v -run TestName ./internal

# Build release binaries for all platforms
make build-release
```

## Testing

Tests use the standard Go testing framework. Test files are colocated with source files (e.g., `plugin.go` has `plugin_test.go`).

## Development Workflow

Before submitting PRs:
```bash
make fmt
make lint
make test
```

## High-Level Architecture

### Core Flow

1. **Input**: PolicyGenerator YAML manifest (custom resource)
2. **Processing**: Plugin.Config() validates and applies defaults → Plugin.Generate() creates policies
3. **Output**: YAML stream with Policy, Placement, and PlacementBinding manifests

### Key Components

**cmd/PolicyGenerator/main.go**
- Entry point that reads PolicyGenerator YAML files
- Calls Plugin.Config() then Plugin.Generate()
- Outputs YAML to stdout (for Kustomize consumption)

**internal/plugin.go** (Plugin struct)
- `Config()`: Validates input, applies defaults, sets base directory
- `Generate()`: Main generation orchestrator
- Creates policies via `createPolicy()`
- Creates policy sets via `createPolicySet()`
- Creates placements (consolidated where possible)
- Creates placement bindings
- Placement consolidation: Tracks cluster/label selectors in `csToPlc` map to reuse placements when selectors match

**internal/types/types.go**
- Defines all configuration structs:
- `PolicyConfig`: Individual policy specification
- `PolicyDefaults`: Default values applied to all policies
- `PolicySetConfig`: Policy set groupings
- `PlacementConfig`: Cluster targeting configuration
- `Manifest`: References to Kubernetes manifests to wrap in policies

**internal/expanders/**
- Policy expanders create additional policies for specific policy engines (Gatekeeper, Kyverno)
- Each expander implements the interface in `expanders.go`
- Enabled by default via `InformGatekeeperPolicies` and `InformKyvernoPolicies` flags

**internal/patches.go**
- Handles Kustomize-style patching of manifests using strategic merge or JSON patches
- OpenAPI schema support for patching non-Kubernetes CRs with list fields

**internal/utils.go**
- Helper functions for manifest processing, YAML/JSON conversion, file operations

### Default Handling

The plugin has a sophisticated defaults system:
- Hard-coded defaults in `defaults` variable (internal/plugin.go:77)
- User-specified `policyDefaults` in PolicyGenerator YAML
- Per-policy overrides in `policies` array
- Per-manifest overrides in `manifests` array

The `applyDefaults()` method (internal/plugin.go:446) cascades these defaults in order, considering:
- Explicit false values (requires checking raw YAML via `unmarshaledConfig`)
- Special relationships (e.g., `orderManifests=true` forces `consolidateManifests=false`)

### Placement Logic

**Consolidation**: Multiple policies can share a Placement if they have identical cluster/label selectors. The `csToPlc` map tracks selector → placement name mappings.

**Kind Selection**: Plugin supports both Placement (cluster.open-cluster-management.io/v1beta1) and deprecated PlacementRule (apps.open-cluster-management.io/v1). Cannot mix in single PolicyGenerator.

**Placement Sources**:
1. External file via `placementPath` or `placementRulePath`
2. Referenced by name via `placementName` or `placementRuleName`
3. Generated from inline `labelSelector` or `clusterSelector`

### Manifest Processing

Manifests can be:
- Individual YAML/JSON files
- Directories (processed recursively)
- Kustomize directories (if not disabled via env var)

The generator wraps each manifest in a ConfigurationPolicy, which is then wrapped in a Policy template. Policy expanders may create additional inform-only policies for Gatekeeper/Kyverno resources.

## Environment Variables

- `POLICY_GEN_ENABLE_HELM`: Set to "true" to enable Helm processing in Kustomize directories
- `POLICY_GEN_DISABLE_LOAD_RESTRICTORS`: Set to "true" to allow Helm directories outside Kustomize path

## Important Validation Rules

- Policy names must be DNS-compliant (RFC 1123)
- Policy namespace + name must be ≤ 63 characters
- Cannot mix Placement and PlacementRule kinds
- `consolidateManifests` and `orderManifests` are mutually exclusive
- `orderManifests` incompatible with `extraDependencies`
- When consolidating manifests, all ConfigurationPolicy options must match at policy level

## Code Modification Guidance

**Adding a Policy Expander**:
1. Create file in `internal/expanders/`
2. Implement the expander interface
3. Register in `getExpanders()` (expanders/expanders.go)
4. Add boolean flag to `PolicyDefaults` and `PolicyConfig` structs (types/types.go)
5. Add default handling in `applyDefaults()` (plugin.go)
6. Update docs/policygenerator-reference.yaml

**Modifying Defaults**:
- Edit `defaults` variable in internal/plugin.go
- Update `applyDefaults()` method for cascade logic
- Check both `PolicyDefaults` and `PolicyConfig` structs

**Adding Manifest Processing**:
- Most logic is in `getPolicyTemplates()` (internal/utils.go)
- Patching handled in internal/patches.go
- Use `unmarshalManifestFile()` for reading YAML/JSON files
42 changes: 25 additions & 17 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.24.0
require (
github.com/google/go-cmp v0.7.0
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/spf13/pflag v1.0.6
github.com/spf13/pflag v1.0.10
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.33.5
sigs.k8s.io/kustomize/api v0.20.1
Expand All @@ -15,30 +15,38 @@ require (
require (
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.22.1 // indirect
github.com/go-openapi/jsonreference v0.21.2 // indirect
github.com/go-openapi/swag v0.25.1 // indirect
github.com/go-openapi/swag/cmdutils v0.25.1 // indirect
github.com/go-openapi/swag/conv v0.25.1 // indirect
github.com/go-openapi/swag/fileutils v0.25.1 // indirect
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
github.com/go-openapi/swag/loading v0.25.1 // indirect
github.com/go-openapi/swag/mangling v0.25.1 // indirect
github.com/go-openapi/swag/netutils v0.25.1 // indirect
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
Expand Down
Loading