Skip to content

Commit eb2f768

Browse files
committed
Set SupportedVersion on GatewayClass (nginx#1301)
Problem: NGF does not set the SupportedVersion condition on the GatewayClass. Solution: Set the SupportedVersion condition on the GatewayClass. This PR adds a new controller that watches the metadata of CRDs. CRD events are filtered using a custom predicate that inspects the gateway.networking.k8s.io/bundle-version annotation. CRDs without this annotation will be ignored. Updates to this annotation will trigger a state change and build a new graph. This required some changes to how we determine if the state has changed in the changeTrackingUpdater.
1 parent b16bfe1 commit eb2f768

35 files changed

+1434
-218
lines changed

conformance/provisioner/provisioner.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ rules:
3030
- gatewayclasses/status
3131
verbs:
3232
- update
33+
- apiGroups:
34+
- apiextensions.k8s.io
35+
resources:
36+
- customresourcedefinitions
37+
verbs:
38+
- list
39+
- watch
3340
---
3441
kind: ClusterRoleBinding
3542
apiVersion: rbac.authorization.k8s.io/v1

deploy/helm-chart/templates/rbac.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ rules:
8080
- get
8181
- update
8282
{{- end }}
83+
- apiGroups:
84+
- apiextensions.k8s.io
85+
resources:
86+
- customresourcedefinitions
87+
verbs:
88+
- list
89+
- watch
8390
---
8491
apiVersion: rbac.authorization.k8s.io/v1
8592
kind: ClusterRoleBinding

deploy/manifests/nginx-gateway.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ rules:
8989
- create
9090
- get
9191
- update
92+
- apiGroups:
93+
- apiextensions.k8s.io
94+
resources:
95+
- customresourcedefinitions
96+
verbs:
97+
- list
98+
- watch
9299
---
93100
# Source: nginx-gateway-fabric/templates/rbac.yaml
94101
apiVersion: rbac.authorization.k8s.io/v1

docs/developer/release-process.md

+12-10
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ To create a new release, follow these steps:
3030
3. Test the main branch for release-readiness. For that, use the `edge` containers, which are built from the main
3131
branch, and the [example applications](/examples).
3232
4. If a problem is found, prepare a fix PR, merge it into the main branch and return to the previous step.
33-
5. Create a release branch with a name that follows the `release-X.Y` format.
34-
6. Prepare and merge a PR into the release branch to update the repo files for the release:
33+
5. If the supported Gateway API minor version has changed since the last release, test NGINX Gateway Fabric with the previous version of the Gateway API CRDs.
34+
6. If a compatibility issue is found, add a note to the release notes explaining that the previous version is not supported.
35+
7. Create a release branch following the `release-X.Y` naming convention.
36+
8. Prepare and merge a PR into the release branch to update the repo files for the release:
3537
1. Update the Helm [Chart.yaml](/deploy/helm-chart/Chart.yaml): the `appVersion` to `X.Y.Z`, the icon and source
3638
URLs to point at `vX.Y.Z`, and bump the `version`.
3739
2. Adjust the `VERSION` variable in the [Makefile](/Makefile) and the `TAG` in the
@@ -52,17 +54,17 @@ To create a new release, follow these steps:
5254
draft of the full changelog. This draft can be found under
5355
the [GitHub releases](https://github.com/nginxinc/nginx-gateway-fabric/releases) after the release branch is
5456
created. Use the previous changelog entries for formatting and content guidance.
55-
7. Create and push the release tag in the format `vX.Y.Z`. As a result, the CI/CD pipeline will:
57+
9. Create and push the release tag in the format `vX.Y.Z`. As a result, the CI/CD pipeline will:
5658
- Build NGF container images with the release tag `X.Y.Z` and push it to the registry.
5759
- Package and publish the Helm chart to the registry.
5860
- Create a GitHub release with an autogenerated changelog and attached release artifacts.
59-
8. Prepare and merge a PR into the main branch to update the [README](/README.md) to include the information about
60-
the latest release and also the [changelog](/CHANGELOG.md). Also update any installation instructions to ensure
61-
that the supported Gateway API and NGF versions are correct. Specifically, helm README and `site/content/includes/installation/install-gateway-api-resources.md`.
62-
9. Close the issue created in Step 1.
63-
10. Ensure that the [associated milestone](https://github.com/nginxinc/nginx-gateway-fabric/milestones) is closed.
64-
11. Verify that published artifacts in the release can be installed properly.
65-
12. Submit the `conformance-profile.yaml` artifact from the release to the [Gateway API repo](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports).
61+
10. Prepare and merge a PR into the main branch to update the [README](/README.md) to include the information about
62+
the latest release and also the [changelog](/CHANGELOG.md). Also update any installation instructions to ensure
63+
that the supported Gateway API and NGF versions are correct. Specifically, helm README and `site/content/includes/installation/install-gateway-api-resources.md`.
64+
11. Close the issue created in Step 1.
65+
12. Ensure that the [associated milestone](https://github.com/nginxinc/nginx-gateway-fabric/milestones) is closed.
66+
13. Verify that published artifacts in the release can be installed properly.
67+
14. Submit the `conformance-profile.yaml` artifact from the release to the [Gateway API repo](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports).
6668
- Create a fork of the repository
6769
- Name the file `nginxinc-nginx-gateway-fabric.yaml` and set `gatewayAPIVersion` in the file to the
6870
supported version by NGF. Also update the site source if necessary (see following example).

internal/framework/conditions/conditions.go

+84-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package conditions
22

33
import (
4+
"fmt"
5+
46
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
57
v1 "sigs.k8s.io/gateway-api/apis/v1"
68
)
@@ -24,7 +26,40 @@ type Condition struct {
2426
Message string
2527
}
2628

27-
// NewDefaultGatewayClassConditions returns the default Conditions that must be present in the status of a GatewayClass.
29+
// DeduplicateConditions removes duplicate conditions based on the condition type.
30+
// The last condition wins. The order of conditions is preserved.
31+
func DeduplicateConditions(conds []Condition) []Condition {
32+
type elem struct {
33+
cond Condition
34+
reverseIdx int
35+
}
36+
37+
uniqueElems := make(map[string]elem)
38+
39+
idx := 0
40+
for i := len(conds) - 1; i >= 0; i-- {
41+
if _, exist := uniqueElems[conds[i].Type]; exist {
42+
continue
43+
}
44+
45+
uniqueElems[conds[i].Type] = elem{
46+
cond: conds[i],
47+
reverseIdx: idx,
48+
}
49+
idx++
50+
}
51+
52+
result := make([]Condition, len(uniqueElems))
53+
54+
for _, el := range uniqueElems {
55+
result[len(result)-el.reverseIdx-1] = el.cond
56+
}
57+
58+
return result
59+
}
60+
61+
// NewDefaultGatewayClassConditions returns Conditions that indicate that the GatewayClass is accepted and that the
62+
// Gateway API CRD versions are supported.
2863
func NewDefaultGatewayClassConditions() []Condition {
2964
return []Condition{
3065
{
@@ -33,6 +68,54 @@ func NewDefaultGatewayClassConditions() []Condition {
3368
Reason: string(v1.GatewayClassReasonAccepted),
3469
Message: "GatewayClass is accepted",
3570
},
71+
{
72+
Type: string(v1.GatewayClassConditionStatusSupportedVersion),
73+
Status: metav1.ConditionTrue,
74+
Reason: string(v1.GatewayClassReasonSupportedVersion),
75+
Message: "Gateway API CRD versions are supported",
76+
},
77+
}
78+
}
79+
80+
// NewGatewayClassSupportedVersionBestEffort returns a Condition that indicates that the GatewayClass is accepted,
81+
// but the Gateway API CRD versions are not supported. This means NGF will attempt to generate configuration,
82+
// but it does not guarantee support.
83+
func NewGatewayClassSupportedVersionBestEffort(recommendedVersion string) []Condition {
84+
return []Condition{
85+
{
86+
Type: string(v1.GatewayClassConditionStatusSupportedVersion),
87+
Status: metav1.ConditionFalse,
88+
Reason: string(v1.GatewayClassReasonUnsupportedVersion),
89+
Message: fmt.Sprintf(
90+
"Gateway API CRD versions are not recommended. Recommended version is %s",
91+
recommendedVersion,
92+
),
93+
},
94+
}
95+
}
96+
97+
// NewGatewayClassUnsupportedVersion returns Conditions that indicate that the GatewayClass is not accepted because
98+
// the Gateway API CRD versions are not supported. NGF will not generate configuration in this case.
99+
func NewGatewayClassUnsupportedVersion(recommendedVersion string) []Condition {
100+
return []Condition{
101+
{
102+
Type: string(v1.GatewayClassConditionStatusAccepted),
103+
Status: metav1.ConditionFalse,
104+
Reason: string(v1.GatewayClassReasonUnsupportedVersion),
105+
Message: fmt.Sprintf(
106+
"Gateway API CRD versions are not supported. Please install version %s",
107+
recommendedVersion,
108+
),
109+
},
110+
{
111+
Type: string(v1.GatewayClassConditionStatusSupportedVersion),
112+
Status: metav1.ConditionFalse,
113+
Reason: string(v1.GatewayClassReasonUnsupportedVersion),
114+
Message: fmt.Sprintf(
115+
"Gateway API CRD versions are not supported. Please install version %s",
116+
recommendedVersion,
117+
),
118+
},
36119
}
37120
}
38121

internal/mode/static/state/conditions/conditions_test.go internal/framework/conditions/conditions_test.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ import (
55

66
. "github.com/onsi/gomega"
77
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8-
9-
"github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions"
108
)
119

1210
func TestDeduplicateConditions(t *testing.T) {
1311
g := NewWithT(t)
1412

15-
conds := []conditions.Condition{
13+
conds := []Condition{
1614
{
1715
Type: "Type1",
1816
Status: metav1.ConditionTrue,
@@ -40,7 +38,7 @@ func TestDeduplicateConditions(t *testing.T) {
4038
},
4139
}
4240

43-
expected := []conditions.Condition{
41+
expected := []Condition{
4442
{
4543
Type: "Type1",
4644
Status: metav1.ConditionFalse,

internal/framework/controller/index/endpointslice_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestServiceNameIndexFunc(t *testing.T) {
5151
func TestServiceNameIndexFuncPanics(t *testing.T) {
5252
defer func() {
5353
g := NewWithT(t)
54-
g.Expect(recover()).ShouldNot(BeNil())
54+
g.Expect(recover()).ToNot(BeNil())
5555
}()
5656

5757
ServiceNameIndexFunc(&v1.Namespace{})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package predicate
2+
3+
import (
4+
"sigs.k8s.io/controller-runtime/pkg/event"
5+
"sigs.k8s.io/controller-runtime/pkg/predicate"
6+
)
7+
8+
// AnnotationPredicate implements a predicate function based on the Annotation.
9+
//
10+
// This predicate will skip the following events:
11+
// 1. Create events that do not contain the Annotation.
12+
// 2. Update events where the Annotation value has not changed.
13+
type AnnotationPredicate struct {
14+
predicate.Funcs
15+
Annotation string
16+
}
17+
18+
// Create filters CreateEvents based on the Annotation.
19+
func (cp AnnotationPredicate) Create(e event.CreateEvent) bool {
20+
if e.Object == nil {
21+
return false
22+
}
23+
24+
_, ok := e.Object.GetAnnotations()[cp.Annotation]
25+
return ok
26+
}
27+
28+
// Update filters UpdateEvents based on the Annotation.
29+
func (cp AnnotationPredicate) Update(e event.UpdateEvent) bool {
30+
if e.ObjectOld == nil || e.ObjectNew == nil {
31+
// this case should not happen
32+
return false
33+
}
34+
35+
oldAnnotationVal := e.ObjectOld.GetAnnotations()[cp.Annotation]
36+
newAnnotationVal := e.ObjectNew.GetAnnotations()[cp.Annotation]
37+
38+
return oldAnnotationVal != newAnnotationVal
39+
}

0 commit comments

Comments
 (0)