Repository: nginx/nginx-gateway-fabric

Issue:
Update to Gateway API 1.2
Update the new release of NGF to use Gateway API 1.2

### Acceptance
- NGF code is updated to use Gateway API 1.2
- NGF documentation references to the version of Gateway API is updated to reference Gateway API 1.2

Hints from issue comments:

Solution patch:
diff --git a/README.md b/README.md
index 2b7ce2e7..996dfe20 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ The following table lists the software versions NGINX Gateway Fabric supports.
 
 | NGINX Gateway Fabric | Gateway API | Kubernetes | NGINX OSS | NGINX Plus |
 |----------------------|-------------|------------|-----------|------------|
-| Edge                 | 1.1.0       | 1.25+      | 1.27.2    | R32        |
+| Edge                 | 1.2.0       | 1.25+      | 1.27.2    | R32        |
 | 1.4.0                | 1.1.0       | 1.25+      | 1.27.1    | R32        |
 | 1.3.0                | 1.1.0       | 1.25+      | 1.27.0    | R32        |
 | 1.2.0                | 1.0.0       | 1.23+      | 1.25.4    | R31        |
diff --git a/cmd/gateway/validation.go b/cmd/gateway/validation.go
index 5559713f..536fa443 100644
--- a/cmd/gateway/validation.go
+++ b/cmd/gateway/validation.go
@@ -14,7 +14,7 @@ import (
 )
 
 const (
-	// Regex from: https://github.com/kubernetes-sigs/gateway-api/blob/v1.1.0/apis/v1/shared_types.go#L647
+	// Regex from: https://github.com/kubernetes-sigs/gateway-api/blob/v1.2.0/apis/v1/shared_types.go#L647
 	controllerNameRegex = `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$` //nolint:lll
 )
 
diff --git a/config/crd/gateway-api/experimental/kustomization.yaml b/config/crd/gateway-api/experimental/kustomization.yaml
index 0eaa2d29..0988b5a4 100644
--- a/config/crd/gateway-api/experimental/kustomization.yaml
+++ b/config/crd/gateway-api/experimental/kustomization.yaml
@@ -1,4 +1,4 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 resources:
-  - https://github.com/kubernetes-sigs/gateway-api/config/crd/experimental?timeout=120&ref=v1.1.0
+  - https://github.com/kubernetes-sigs/gateway-api/config/crd/experimental?timeout=120&ref=v1.2.0
diff --git a/config/crd/gateway-api/standard/kustomization.yaml b/config/crd/gateway-api/standard/kustomization.yaml
index 4f2165d7..aa5be5af 100644
--- a/config/crd/gateway-api/standard/kustomization.yaml
+++ b/config/crd/gateway-api/standard/kustomization.yaml
@@ -1,4 +1,4 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 resources:
-- https://github.com/kubernetes-sigs/gateway-api/config/crd?timeout=120&ref=v1.1.0
+- https://github.com/kubernetes-sigs/gateway-api/config/crd?timeout=120&ref=v1.2.0
diff --git a/go.mod b/go.mod
index 42ea08c6..6a1df43c 100644
--- a/go.mod
+++ b/go.mod
@@ -25,7 +25,7 @@ require (
 	k8s.io/client-go v0.31.1
 	k8s.io/klog/v2 v2.130.1
 	sigs.k8s.io/controller-runtime v0.19.0
-	sigs.k8s.io/gateway-api v1.1.0
+	sigs.k8s.io/gateway-api v1.2.0
 )
 
 require (
@@ -84,7 +84,7 @@ require (
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
-	google.golang.org/grpc v1.66.1 // indirect
+	google.golang.org/grpc v1.66.2 // indirect
 	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
diff --git a/go.sum b/go.sum
index c5e030f0..f77ec09c 100644
--- a/go.sum
+++ b/go.sum
@@ -198,8 +198,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
-google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
-google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
+google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
 google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -230,8 +230,8 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1
 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
 sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
-sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
-sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
+sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8=
+sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
diff --git a/internal/framework/gatewayclass/validate.go b/internal/framework/gatewayclass/validate.go
index 14bef982..57b249de 100644
--- a/internal/framework/gatewayclass/validate.go
+++ b/internal/framework/gatewayclass/validate.go
@@ -13,7 +13,7 @@ const (
 	// BundleVersionAnnotation is the annotation on Gateway API CRDs that contains the installed version.
 	BundleVersionAnnotation = "gateway.networking.k8s.io/bundle-version"
 	// SupportedVersion is the supported version of the Gateway API CRDs.
-	SupportedVersion = "v1.1.0"
+	SupportedVersion = "v1.2.0"
 )
 
 var gatewayCRDs = map[string]apiVersion{
diff --git a/internal/mode/static/state/graph/grpcroute.go b/internal/mode/static/state/graph/grpcroute.go
index 4d4ed05b..9a9c81f6 100644
--- a/internal/mode/static/state/graph/grpcroute.go
+++ b/internal/mode/static/state/graph/grpcroute.go
@@ -232,7 +232,7 @@ func validateGRPCMatch(
 
 	for j, h := range match.Headers {
 		headerPath := matchPath.Child("headers").Index(j)
-		allErrs = append(allErrs, validateHeaderMatch(validator, h.Type, string(h.Name), h.Value, headerPath)...)
+		allErrs = append(allErrs, validateGRPCHeaderMatch(validator, h.Type, string(h.Name), h.Value, headerPath)...)
 	}
 
 	return allErrs
@@ -277,3 +277,27 @@ func validateGRPCMethodMatch(
 	}
 	return allErrs
 }
+
+func validateGRPCHeaderMatch(
+	validator validation.HTTPFieldsValidator,
+	headerType *v1.GRPCHeaderMatchType,
+	headerName, headerValue string,
+	headerPath *field.Path,
+) field.ErrorList {
+	var allErrs field.ErrorList
+
+	if headerType == nil {
+		allErrs = append(allErrs, field.Required(headerPath.Child("type"), "cannot be empty"))
+	} else if *headerType != v1.GRPCHeaderMatchExact {
+		valErr := field.NotSupported(
+			headerPath.Child("type"),
+			*headerType,
+			[]string{string(v1.GRPCHeaderMatchExact)},
+		)
+		allErrs = append(allErrs, valErr)
+	}
+
+	allErrs = append(allErrs, validateHeaderMatchNameAndValue(validator, headerName, headerValue, headerPath)...)
+
+	return allErrs
+}
diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go
index 9a4dbe3a..4b2e767c 100644
--- a/internal/mode/static/state/graph/route_common.go
+++ b/internal/mode/static/state/graph/route_common.go
@@ -868,6 +868,17 @@ func validateHeaderMatch(
 		allErrs = append(allErrs, valErr)
 	}
 
+	allErrs = append(allErrs, validateHeaderMatchNameAndValue(validator, headerName, headerValue, headerPath)...)
+
+	return allErrs
+}
+
+func validateHeaderMatchNameAndValue(
+	validator validation.HTTPFieldsValidator,
+	headerName, headerValue string,
+	headerPath *field.Path,
+) field.ErrorList {
+	var allErrs field.ErrorList
 	if err := validator.ValidateHeaderNameInMatch(headerName); err != nil {
 		valErr := field.Invalid(headerPath.Child("name"), headerName, err.Error())
 		allErrs = append(allErrs, valErr)
@@ -877,7 +888,6 @@ func validateHeaderMatch(
 		valErr := field.Invalid(headerPath.Child("value"), headerValue, err.Error())
 		allErrs = append(allErrs, valErr)
 	}
-
 	return allErrs
 }
 



Test patch:
diff --git a/internal/mode/static/state/graph/grpcroute_test.go b/internal/mode/static/state/graph/grpcroute_test.go
index 1045939b..782856dc 100644
--- a/internal/mode/static/state/graph/grpcroute_test.go
+++ b/internal/mode/static/state/graph/grpcroute_test.go
@@ -42,7 +42,7 @@ func createGRPCHeadersMatch(headerType, headerName, headerValue string) v1.GRPCR
 			{
 				Headers: []v1.GRPCHeaderMatch{
 					{
-						Type:  (*v1.HeaderMatchType)(&headerType),
+						Type:  (*v1.GRPCHeaderMatchType)(&headerType),
 						Name:  v1.GRPCHeaderName(headerName),
 						Value: headerValue,
 					},
@@ -242,6 +242,19 @@ func TestBuildGRPCRoute(t *testing.T) {
 	methodMatchNilType := createGRPCMethodMatch("myService", "myMethod", "nilType")
 	headersMatchInvalid := createGRPCHeadersMatch("", "MyHeader", "SomeValue")
 
+	headersMatchEmptyType := v1.GRPCRouteRule{
+		Matches: []v1.GRPCRouteMatch{
+			{
+				Headers: []v1.GRPCHeaderMatch{
+					{
+						Name:  v1.GRPCHeaderName("MyHeader"),
+						Value: "SomeValue",
+					},
+				},
+			},
+		},
+	}
+
 	grBoth := createGRPCRoute(
 		"gr-1",
 		gatewayNsName.Name,
@@ -290,12 +303,19 @@ func TestBuildGRPCRoute(t *testing.T) {
 		"example.com",
 		[]v1.GRPCRouteRule{methodMatchNilType},
 	)
-	grInvalidHeadersEmptyType := createGRPCRoute(
+	grInvalidHeadersInvalidType := createGRPCRoute(
 		"gr-1",
 		gatewayNsName.Name,
 		"example.com",
 		[]v1.GRPCRouteRule{headersMatchInvalid},
 	)
+
+	grInvalidHeadersEmptyType := createGRPCRoute(
+		"gr-1",
+		gatewayNsName.Name,
+		"example.com",
+		[]v1.GRPCRouteRule{headersMatchEmptyType},
+	)
 	grOneInvalid := createGRPCRoute(
 		"gr-1",
 		gatewayNsName.Name,
@@ -736,6 +756,44 @@ func TestBuildGRPCRoute(t *testing.T) {
 			},
 			name: "invalid headers and valid method",
 		},
+		{
+			validator: createAllValidValidator(),
+			gr:        grInvalidHeadersInvalidType,
+			expected: &L7Route{
+				Source:     grInvalidHeadersInvalidType,
+				RouteType:  RouteTypeGRPC,
+				Valid:      false,
+				Attachable: true,
+				ParentRefs: []ParentRef{
+					{
+						Idx:         0,
+						Gateway:     gatewayNsName,
+						SectionName: grInvalidHeadersInvalidType.Spec.ParentRefs[0].SectionName,
+					},
+				},
+				Conditions: []conditions.Condition{
+					staticConds.NewRouteUnsupportedValue(
+						`All rules are invalid: spec.rules[0].matches[0].headers[0].type: ` +
+							`Unsupported value: "": supported values: "Exact"`,
+					),
+				},
+				Spec: L7RouteSpec{
+					Hostnames: grInvalidHeadersInvalidType.Spec.Hostnames,
+					Rules: []RouteRule{
+						{
+							ValidMatches: false,
+							Filters: RouteRuleFilters{
+								Valid:   true,
+								Filters: []Filter{},
+							},
+							Matches:          convertGRPCMatches(grInvalidHeadersInvalidType.Spec.Rules[0].Matches),
+							RouteBackendRefs: []RouteBackendRef{},
+						},
+					},
+				},
+			},
+			name: "invalid headers with invalid type",
+		},
 		{
 			validator: createAllValidValidator(),
 			gr:        grInvalidHeadersEmptyType,
@@ -754,7 +812,7 @@ func TestBuildGRPCRoute(t *testing.T) {
 				Conditions: []conditions.Condition{
 					staticConds.NewRouteUnsupportedValue(
 						`All rules are invalid: spec.rules[0].matches[0].headers[0].type: ` +
-							`Unsupported value: "": supported values: "Exact"`,
+							`Required value: cannot be empty`,
 					),
 				},
 				Spec: L7RouteSpec{
@@ -772,7 +830,7 @@ func TestBuildGRPCRoute(t *testing.T) {
 					},
 				},
 			},
-			name: "invalid headers with empty type",
+			name: "invalid headers with no header type specified",
 		},
 		{
 			validator: createAllValidValidator(),
diff --git a/tests/go.mod b/tests/go.mod
index 7cf83354..585c837a 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -14,7 +14,7 @@ require (
 	k8s.io/apimachinery v0.31.1
 	k8s.io/client-go v0.31.1
 	sigs.k8s.io/controller-runtime v0.19.0
-	sigs.k8s.io/gateway-api v1.1.0
+	sigs.k8s.io/gateway-api v1.2.0
 	sigs.k8s.io/yaml v1.4.0
 )
 
@@ -46,7 +46,7 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.17.9 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/miekg/dns v1.1.61 // indirect
+	github.com/miekg/dns v1.1.62 // indirect
 	github.com/moby/spdystream v0.4.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -72,7 +72,7 @@ require (
 	golang.org/x/tools v0.25.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
-	google.golang.org/grpc v1.66.1 // indirect
+	google.golang.org/grpc v1.66.2 // indirect
 	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/tests/go.sum b/tests/go.sum
index a11306d5..36d5481c 100644
--- a/tests/go.sum
+++ b/tests/go.sum
@@ -77,8 +77,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
-github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
+github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
+github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
 github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
 github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -187,8 +187,8 @@ gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJ
 gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
-google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
-google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
+google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
 google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -221,8 +221,8 @@ pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
 pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
 sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
 sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
-sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
-sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
+sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8=
+sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=