Skip to content

✨ Add feature gates support for experimental API fields #4973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
- [Object/DeepCopy](./reference/markers/object.md)
- [RBAC](./reference/markers/rbac.md)
- [Scaffold](./reference/markers/scaffold.md)
- [Feature Gates](./reference/markers/feature-gates.md)

- [controller-gen CLI](./reference/controller-gen.md)
- [completion](./reference/completion.md)
Expand Down
4 changes: 2 additions & 2 deletions docs/book/src/reference/markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ controller-gen:

- `make manifests` generates Kubernetes object YAML, like
[CustomResourceDefinitions](./markers/crd.md),
[WebhookConfigurations](./markers/webhook.md), and [RBAC
roles](./markers/rbac.md).
[WebhookConfigurations](./markers/webhook.md), [RBAC
roles](./markers/rbac.md), and [Feature Gates](./markers/feature-gates.md).

- `make generate` generates code, like [runtime.Object/DeepCopy
implementations](./markers/object.md).
Expand Down
186 changes: 186 additions & 0 deletions docs/book/src/reference/markers/feature-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Feature Gates

Feature gates allow you to enable or disable experimental features in your Kubebuilder controllers. This is similar to how Kubernetes core uses feature gates to manage experimental functionality.

## Quick Start

### Marking Fields

```go
type MyResourceSpec struct {
// Standard field
Name string `json:"name"`

// Experimental field
// +feature-gate experimental-bar
Bar *string `json:"bar,omitempty"`
}
```

### Running with Feature Gates

```bash
# Enable feature gates
./manager --feature-gates=experimental-bar

# Multiple gates
./manager --feature-gates=experimental-bar,advanced-features

# Mixed states
./manager --feature-gates=experimental-bar=true,advanced-features=false
```

## Overview

Feature gates provide a mechanism to:
- Control the availability of experimental features
- Enable gradual rollout of new functionality
- Maintain backward compatibility during API evolution

## Usage

### Marking Fields with Feature Gates

Use the `+feature-gate` marker to mark experimental fields in your API types:

```go
type MyResourceSpec struct {
// Standard field
Name string `json:"name"`

// Experimental field that requires the "experimental-bar" feature gate
// +feature-gate experimental-bar
Bar *string `json:"bar,omitempty"`
}
```

### Running with Feature Gates

Enable feature gates when running your controller:

```bash
# Enable a single feature gate
./manager --feature-gates=experimental-bar

# Enable multiple feature gates
./manager --feature-gates=experimental-bar,advanced-features

# Mixed enabled/disabled states
./manager --feature-gates=experimental-bar=true,advanced-features=false
```

### Feature Gate Formats

The `--feature-gates` flag accepts:
- `feature1` - Enables feature1 (defaults to true)
- `feature1=true` - Explicitly enables feature1
- `feature1=false` - Explicitly disables feature1
- `feature1,feature2` - Enables both features
- `feature1=true,feature2=false` - Mixed states

## Best Practices

### Naming Conventions

Use descriptive, lowercase names with hyphens:
- `experimental-bar`
- `advanced-features`
- `ExperimentalBar`
- `advanced_features`

### Documentation

Always document feature-gated fields:

```go
// Bar is an experimental field that requires the "experimental-bar" feature gate
// +feature-gate experimental-bar
// +optional
Bar *string `json:"bar,omitempty"`
```

### Gradual Rollout Strategy

1. **Alpha**: Feature behind feature gate
2. **Beta**: Feature enabled by default, gate for opt-out
3. **Stable**: Remove feature gate, feature always available

## Future Integration

When [controller-tools supports feature gates](https://github.com/kubernetes-sigs/controller-tools/issues/1238), you'll be able to use:

```go
// +kubebuilder:feature-gate=experimental-bar
// +optional
Bar *string `json:"bar,omitempty"`
```

This will automatically exclude the field from CRD schemas when the feature gate is disabled.

## Examples

### Basic Example

```go
type CronJobSpec struct {
// Standard fields
Schedule string `json:"schedule"`

// Experimental timezone support
// +feature-gate timezone-support
Timezone *string `json:"timezone,omitempty"`
}
```

### Controller Logic

```go
func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// Check if timezone feature is enabled
if featureGates.IsEnabled("timezone-support") {
// Use timezone-aware scheduling
return r.reconcileWithTimezone(ctx, req)
}

// Fall back to standard scheduling
return r.reconcileStandard(ctx, req)
}
```

## Troubleshooting

### Common Issues

1. **Feature gate not discovered**: Ensure the `+feature-gate` marker is on the line before the field
2. **Invalid feature gate name**: Use lowercase with hyphens only
3. **Validation errors**: Check that all specified gates are available

### Debugging

Enable debug logging to see feature gate discovery:

```bash
./manager --feature-gates=experimental-bar --zap-log-level=debug
```

## Implementation Status

### Production Ready

- Feature gate discovery and validation
- Controller integration with `--feature-gates` flag
- Scaffolding integration for new projects

### Future Enhancement

- CRD schema modification (requires [controller-tools support](https://github.com/kubernetes-sigs/controller-tools/issues/1238))

When [controller-tools supports feature gates](https://github.com/kubernetes-sigs/controller-tools/issues/1238), you'll be able to use:

```go
// +kubebuilder:feature-gate=experimental-bar
// +optional
Bar *string `json:"bar,omitempty"`
```

This will automatically exclude the field from CRD schemas when the feature gate is disabled.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/spf13/afero v1.14.0
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.10.0
golang.org/x/mod v0.26.0
golang.org/x/text v0.27.0
golang.org/x/tools v0.35.0
Expand All @@ -19,6 +20,7 @@ require (

require (
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
Expand All @@ -27,6 +29,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
Expand Down
18 changes: 17 additions & 1 deletion hack/docs/internal/cronjob-tutorial/generate_cronjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,28 @@ type CronJob struct {`+`
*/`)
hackutils.CheckError("fixing schema for cronjob_types.go", err)

// fix lint
// fix lint - handle the pattern with or without feature-gated fields
err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"),
`/
// Example of a feature-gated field:
// Bar is an experimental field that requires the "experimental-bar" feature gate to be enabled
// TODO: When controller-tools supports feature gates (issue #1238), use:
// +kubebuilder:feature-gate=experimental-bar
// +feature-gate experimental-bar
// +optional
Bar *string `+"`"+`json:"bar,omitempty"`+"`"+`
}`, "/")
if err != nil {
// Fallback to the original pattern if the feature-gated field is not present
err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_types.go"),
`/
}`, "/")
}
hackutils.CheckError("fixing cronjob_types.go end of status", err)
}

Expand Down
18 changes: 16 additions & 2 deletions hack/docs/internal/multiversion-tutorial/generate_multiversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,11 +735,25 @@ We'll leave our spec largely unchanged, except to change the schedule field to a

err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, path),
`// foo is an example field of CronJob. Edit cronjob_types.go to remove/update
`// Example of a feature-gated field:
// Bar is an experimental field that requires the "experimental-bar" feature gate to be enabled
// TODO: When controller-tools supports feature gates (issue #1238), use:
// +kubebuilder:feature-gate=experimental-bar
// +feature-gate experimental-bar
// +optional
Foo *string `+"`json:\"foo,omitempty\"`",
Bar *string `+"`"+`json:"bar,omitempty"`+"`",
cronjobSpecMore,
)
if err != nil {
// Fallback to the original pattern if the feature-gated field is not present
err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, path),
`// foo is an example field of CronJob. Edit cronjob_types.go to remove/update
// +optional
Foo *string `+"`json:\"foo,omitempty\"`",
cronjobSpecMore,
)
}
hackutils.CheckError("replace Foo with cronjob spec fields", err)

err = pluginutil.ReplaceInFile(
Expand Down
Loading
Loading