Skip to content

Commit 0414f0a

Browse files
committed
feat: implement type-level feature gates for CRDs
Add +kubebuilder:featuregate marker support for conditional CRD type generation with OR/AND expression support. Fixes #600
1 parent e3a9853 commit 0414f0a

21 files changed

+1370
-4
lines changed

pkg/crd/gen.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
163163
}
164164

165165
// TODO: allow selecting a specific object
166-
kubeKinds := FindKubeKinds(parser, metav1Pkg)
166+
kubeKinds := FindKubeKinds(parser, metav1Pkg, featureGates)
167167
if len(kubeKinds) == 0 {
168168
// no objects in the roots
169169
return nil
@@ -281,8 +281,8 @@ func FindMetav1(roots []*loader.Package) *loader.Package {
281281

282282
// FindKubeKinds locates all types that contain TypeMeta and ObjectMeta
283283
// (and thus may be a Kubernetes object), and returns the corresponding
284-
// group-kinds.
285-
func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) []schema.GroupKind {
284+
// group-kinds that are not filtered out by feature gates.
285+
func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package, featureGates featuregate.FeatureGateMap) []schema.GroupKind {
286286
// TODO(directxman12): technically, we should be finding metav1 per-package
287287
kubeKinds := map[schema.GroupKind]struct{}{}
288288
for typeIdent, info := range parser.Types {
@@ -334,6 +334,19 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) []schema.GroupKind
334334
continue
335335
}
336336

337+
// Check type-level feature gate marker
338+
if featureGateMarker := info.Markers.Get("kubebuilder:featuregate"); featureGateMarker != nil {
339+
if typeFeatureGate, ok := featureGateMarker.(crdmarkers.TypeFeatureGate); ok {
340+
gateName := string(typeFeatureGate)
341+
// Create evaluator to handle complex expressions (OR/AND logic)
342+
evaluator := featuregate.NewFeatureGateEvaluator(featureGates)
343+
if !evaluator.EvaluateExpression(gateName) {
344+
// Skip this type as its feature gate expression is not satisfied
345+
continue
346+
}
347+
}
348+
}
349+
337350
groupKind := schema.GroupKind{
338351
Group: parser.GroupVersions[pkg].Group,
339352
Kind: typeIdent.Name,

pkg/crd/markers/crd.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ var CRDMarkers = []*definitionWithHelp{
5757

5858
must(markers.MakeDefinition("kubebuilder:selectablefield", markers.DescribesType, SelectableField{})).
5959
WithHelp(SelectableField{}.Help()),
60+
61+
must(markers.MakeDefinition("kubebuilder:featuregate", markers.DescribesType, TypeFeatureGate(""))).
62+
WithHelp(TypeFeatureGate("").Help()),
6063
}
6164

6265
// TODO: categories and singular used to be annotations types
@@ -419,3 +422,22 @@ func (s SelectableField) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, ve
419422

420423
return nil
421424
}
425+
426+
// +controllertools:marker:generateHelp:category="CRD feature gates"
427+
428+
// TypeFeatureGate marks an entire CRD type to be conditionally generated based on feature gate enablement.
429+
// Types marked with +kubebuilder:featuregate will only be included in generated CRDs
430+
// when the specified feature gate is enabled via the crd:featureGates parameter.
431+
//
432+
// Single gate format: +kubebuilder:featuregate=alpha
433+
// OR expression: +kubebuilder:featuregate=alpha|beta
434+
// AND expression: +kubebuilder:featuregate=alpha&beta
435+
// Complex expression: +kubebuilder:featuregate=(alpha&beta)|gamma
436+
type TypeFeatureGate string
437+
438+
// ApplyToCRD does nothing for type feature gates - they are processed by the generator
439+
// to conditionally include/exclude entire CRDs during generation.
440+
func (TypeFeatureGate) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error {
441+
// Type feature gates are handled during CRD discovery/generation, not during CRD spec modification
442+
return nil
443+
}

pkg/crd/markers/zz_generated.markerhelp.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: (devel)
7+
name: alphagateds.
8+
spec:
9+
group: ""
10+
names:
11+
kind: AlphaGated
12+
listKind: AlphaGatedList
13+
plural: alphagateds
14+
singular: alphagated
15+
scope: Namespace
16+
versions:
17+
- name: ""
18+
schema:
19+
openAPIV3Schema:
20+
description: AlphaGated is only generated when alpha feature gate is enabled
21+
properties:
22+
apiVersion:
23+
description: |-
24+
APIVersion defines the versioned schema of this representation of an object.
25+
Servers should convert recognized schemas to the latest internal value, and
26+
may reject unrecognized values.
27+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
28+
type: string
29+
kind:
30+
description: |-
31+
Kind is a string value representing the REST resource this object represents.
32+
Servers may infer this from the endpoint the client submits requests to.
33+
Cannot be updated.
34+
In CamelCase.
35+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
36+
type: string
37+
metadata:
38+
type: object
39+
spec:
40+
description: AlphaGatedSpec defines a CRD that's only generated when alpha
41+
gate is enabled
42+
properties:
43+
alphaField:
44+
type: string
45+
required:
46+
- alphaField
47+
type: object
48+
status:
49+
description: AlphaGatedStatus defines the observed state of AlphaGated
50+
properties:
51+
alphaReady:
52+
type: boolean
53+
required:
54+
- alphaReady
55+
type: object
56+
type: object
57+
served: true
58+
storage: true
59+
subresources:
60+
status: {}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: (devel)
7+
name: alwaysons.
8+
spec:
9+
group: ""
10+
names:
11+
kind: AlwaysOn
12+
listKind: AlwaysOnList
13+
plural: alwaysons
14+
singular: alwayson
15+
scope: Namespace
16+
versions:
17+
- name: ""
18+
schema:
19+
openAPIV3Schema:
20+
description: AlwaysOn is always generated since it has no feature gate marker
21+
properties:
22+
apiVersion:
23+
description: |-
24+
APIVersion defines the versioned schema of this representation of an object.
25+
Servers should convert recognized schemas to the latest internal value, and
26+
may reject unrecognized values.
27+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
28+
type: string
29+
kind:
30+
description: |-
31+
Kind is a string value representing the REST resource this object represents.
32+
Servers may infer this from the endpoint the client submits requests to.
33+
Cannot be updated.
34+
In CamelCase.
35+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
36+
type: string
37+
metadata:
38+
type: object
39+
spec:
40+
description: AlwaysOnSpec defines a CRD that's always generated (no feature
41+
gate)
42+
properties:
43+
name:
44+
type: string
45+
required:
46+
- name
47+
type: object
48+
status:
49+
description: AlwaysOnStatus defines the observed state of AlwaysOn
50+
properties:
51+
ready:
52+
type: boolean
53+
required:
54+
- ready
55+
type: object
56+
type: object
57+
served: true
58+
storage: true
59+
subresources:
60+
status: {}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: (devel)
7+
name: orgateds.
8+
spec:
9+
group: ""
10+
names:
11+
kind: OrGated
12+
listKind: OrGatedList
13+
plural: orgateds
14+
singular: orgated
15+
scope: Namespace
16+
versions:
17+
- name: ""
18+
schema:
19+
openAPIV3Schema:
20+
description: OrGated is generated when either alpha OR beta feature gate is
21+
enabled
22+
properties:
23+
apiVersion:
24+
description: |-
25+
APIVersion defines the versioned schema of this representation of an object.
26+
Servers should convert recognized schemas to the latest internal value, and
27+
may reject unrecognized values.
28+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
29+
type: string
30+
kind:
31+
description: |-
32+
Kind is a string value representing the REST resource this object represents.
33+
Servers may infer this from the endpoint the client submits requests to.
34+
Cannot be updated.
35+
In CamelCase.
36+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
37+
type: string
38+
metadata:
39+
type: object
40+
spec:
41+
description: OrGatedSpec defines a CRD that's generated when either alpha
42+
OR beta is enabled
43+
properties:
44+
orField:
45+
type: string
46+
required:
47+
- orField
48+
type: object
49+
status:
50+
description: OrGatedStatus defines the observed state of OrGated
51+
properties:
52+
orReady:
53+
type: boolean
54+
required:
55+
- orReady
56+
type: object
57+
type: object
58+
served: true
59+
storage: true
60+
subresources:
61+
status: {}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: (devel)
7+
name: alwaysons.
8+
spec:
9+
group: ""
10+
names:
11+
kind: AlwaysOn
12+
listKind: AlwaysOnList
13+
plural: alwaysons
14+
singular: alwayson
15+
scope: Namespace
16+
versions:
17+
- name: ""
18+
schema:
19+
openAPIV3Schema:
20+
description: AlwaysOn is always generated since it has no feature gate marker
21+
properties:
22+
apiVersion:
23+
description: |-
24+
APIVersion defines the versioned schema of this representation of an object.
25+
Servers should convert recognized schemas to the latest internal value, and
26+
may reject unrecognized values.
27+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
28+
type: string
29+
kind:
30+
description: |-
31+
Kind is a string value representing the REST resource this object represents.
32+
Servers may infer this from the endpoint the client submits requests to.
33+
Cannot be updated.
34+
In CamelCase.
35+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
36+
type: string
37+
metadata:
38+
type: object
39+
spec:
40+
description: AlwaysOnSpec defines a CRD that's always generated (no feature
41+
gate)
42+
properties:
43+
name:
44+
type: string
45+
required:
46+
- name
47+
type: object
48+
status:
49+
description: AlwaysOnStatus defines the observed state of AlwaysOn
50+
properties:
51+
ready:
52+
type: boolean
53+
required:
54+
- ready
55+
type: object
56+
type: object
57+
served: true
58+
storage: true
59+
subresources:
60+
status: {}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: (devel)
7+
name: betagateds.
8+
spec:
9+
group: ""
10+
names:
11+
kind: BetaGated
12+
listKind: BetaGatedList
13+
plural: betagateds
14+
singular: betagated
15+
scope: Namespace
16+
versions:
17+
- name: ""
18+
schema:
19+
openAPIV3Schema:
20+
description: BetaGated is only generated when beta feature gate is enabled
21+
properties:
22+
apiVersion:
23+
description: |-
24+
APIVersion defines the versioned schema of this representation of an object.
25+
Servers should convert recognized schemas to the latest internal value, and
26+
may reject unrecognized values.
27+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
28+
type: string
29+
kind:
30+
description: |-
31+
Kind is a string value representing the REST resource this object represents.
32+
Servers may infer this from the endpoint the client submits requests to.
33+
Cannot be updated.
34+
In CamelCase.
35+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
36+
type: string
37+
metadata:
38+
type: object
39+
spec:
40+
description: BetaGatedSpec defines a CRD that's only generated when beta
41+
gate is enabled
42+
properties:
43+
betaField:
44+
type: string
45+
required:
46+
- betaField
47+
type: object
48+
status:
49+
description: BetaGatedStatus defines the observed state of BetaGated
50+
properties:
51+
betaReady:
52+
type: boolean
53+
required:
54+
- betaReady
55+
type: object
56+
type: object
57+
served: true
58+
storage: true
59+
subresources:
60+
status: {}

0 commit comments

Comments
 (0)