Skip to content

Commit 5a3ebd5

Browse files
committed
Add SnippetsPolicy validation and processing logic
1 parent a0dcec4 commit 5a3ebd5

File tree

9 files changed

+429
-105
lines changed

9 files changed

+429
-105
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2025 The NGINX Gateway Fabric Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package snippetspolicy
18+
19+
import (
20+
"fmt"
21+
22+
"k8s.io/apimachinery/pkg/util/validation/field"
23+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
24+
25+
ngfAPI "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1"
26+
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/nginx/config/policies"
27+
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/state/conditions"
28+
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/helpers"
29+
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/kinds"
30+
)
31+
32+
// Validator validates a SnippetsPolicy.
33+
// Implements policies.Validator interface.
34+
type Validator struct{}
35+
36+
// NewValidator returns a new instance of Validator.
37+
func NewValidator() *Validator {
38+
return &Validator{}
39+
}
40+
41+
// Validate validates the spec of a SnippetsPolicy.
42+
func (v *Validator) Validate(policy policies.Policy) []conditions.Condition {
43+
sp := helpers.MustCastObject[*ngfAPI.SnippetsPolicy](policy)
44+
45+
targetRefPath := field.NewPath("spec").Child("targetRef")
46+
supportedKinds := []gatewayv1.Kind{kinds.Gateway}
47+
supportedGroups := []gatewayv1.Group{gatewayv1.GroupName}
48+
49+
// Validate TargetRef
50+
if err := policies.ValidateTargetRef(sp.Spec.TargetRef.LocalPolicyTargetReference, targetRefPath, supportedGroups, supportedKinds); err != nil {
51+
return []conditions.Condition{conditions.NewPolicyInvalid(err.Error())}
52+
}
53+
54+
// Validate Snippets
55+
if err := validateSnippets(sp.Spec.Snippets); err != nil {
56+
return []conditions.Condition{conditions.NewPolicyInvalid(err.Error())}
57+
}
58+
59+
return nil
60+
}
61+
62+
// ValidateGlobalSettings validates a SnippetsPolicy with respect to the NginxProxy global settings.
63+
func (v *Validator) ValidateGlobalSettings(
64+
_ policies.Policy,
65+
_ *policies.GlobalSettings,
66+
) []conditions.Condition {
67+
return nil
68+
}
69+
70+
// Conflicts returns true if the two SnippetsPolicies conflict.
71+
// SnippetsPolicies are merged by lexicographic order, so they don't inherently conflict
72+
// in a way that prevents them from being applied together (structurally).
73+
// Detailed logical conflicts (e.g. conflicting NGINX directives) are caught by nginx -t.
74+
func (v *Validator) Conflicts(polA, polB policies.Policy) bool {
75+
return false
76+
}
77+
78+
func validateSnippets(snippets []ngfAPI.Snippet) error {
79+
seenContexts := make(map[ngfAPI.NginxContext]struct{})
80+
for _, snippet := range snippets {
81+
if _, exists := seenContexts[snippet.Context]; exists {
82+
return fmt.Errorf("duplicate context %q", snippet.Context)
83+
}
84+
seenContexts[snippet.Context] = struct{}{}
85+
}
86+
return nil
87+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package snippetspolicy_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/gomega"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
9+
10+
ngfAPI "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1"
11+
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/nginx/config/policies/snippetspolicy"
12+
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/state/conditions"
13+
"github.com/nginx/nginx-gateway-fabric/v2/internal/framework/kinds"
14+
)
15+
16+
type policyModFunc func(policy *ngfAPI.SnippetsPolicy) *ngfAPI.SnippetsPolicy
17+
18+
func createValidPolicy() *ngfAPI.SnippetsPolicy {
19+
return &ngfAPI.SnippetsPolicy{
20+
ObjectMeta: metav1.ObjectMeta{
21+
Namespace: "default",
22+
Name: "test-policy",
23+
},
24+
Spec: ngfAPI.SnippetsPolicySpec{
25+
TargetRef: ngfAPI.SnippetsPolicyTargetRef{
26+
LocalPolicyTargetReference: gatewayv1.LocalPolicyTargetReference{
27+
Group: gatewayv1.GroupName,
28+
Kind: kinds.Gateway,
29+
Name: "test-gateway",
30+
},
31+
},
32+
Snippets: []ngfAPI.Snippet{
33+
{
34+
Context: ngfAPI.NginxContextMain,
35+
Value: "worker_processes 1;",
36+
},
37+
{
38+
Context: ngfAPI.NginxContextHTTP,
39+
Value: "log_format custom '...';",
40+
},
41+
},
42+
},
43+
Status: gatewayv1.PolicyStatus{},
44+
}
45+
}
46+
47+
func createModifiedPolicy(mod policyModFunc) *ngfAPI.SnippetsPolicy {
48+
return mod(createValidPolicy())
49+
}
50+
51+
func TestValidator_Validate(t *testing.T) {
52+
t.Parallel()
53+
tests := []struct {
54+
name string
55+
policy *ngfAPI.SnippetsPolicy
56+
expConditions []conditions.Condition
57+
}{
58+
{
59+
name: "valid policy",
60+
policy: createValidPolicy(),
61+
expConditions: nil,
62+
},
63+
{
64+
name: "invalid target ref - unsupported group",
65+
policy: createModifiedPolicy(func(p *ngfAPI.SnippetsPolicy) *ngfAPI.SnippetsPolicy {
66+
p.Spec.TargetRef.Group = "unsupported.group"
67+
return p
68+
}),
69+
expConditions: []conditions.Condition{
70+
conditions.NewPolicyInvalid("spec.targetRef.group: Unsupported value: \"unsupported.group\": supported values: \"gateway.networking.k8s.io\""),
71+
},
72+
},
73+
{
74+
name: "invalid target ref - unsupported kind",
75+
policy: createModifiedPolicy(func(p *ngfAPI.SnippetsPolicy) *ngfAPI.SnippetsPolicy {
76+
p.Spec.TargetRef.Kind = "UnsupportedKind"
77+
return p
78+
}),
79+
expConditions: []conditions.Condition{
80+
conditions.NewPolicyInvalid("spec.targetRef.kind: Unsupported value: \"UnsupportedKind\": supported values: \"Gateway\""),
81+
},
82+
},
83+
{
84+
name: "duplicate context",
85+
policy: createModifiedPolicy(func(p *ngfAPI.SnippetsPolicy) *ngfAPI.SnippetsPolicy {
86+
p.Spec.Snippets = append(p.Spec.Snippets, ngfAPI.Snippet{
87+
Context: ngfAPI.NginxContextMain,
88+
Value: "another snippet;",
89+
})
90+
return p
91+
}),
92+
expConditions: []conditions.Condition{
93+
conditions.NewPolicyInvalid("duplicate context \"main\""),
94+
},
95+
},
96+
}
97+
98+
v := snippetspolicy.NewValidator()
99+
100+
for _, test := range tests {
101+
t.Run(test.name, func(t *testing.T) {
102+
t.Parallel()
103+
g := NewWithT(t)
104+
105+
conds := v.Validate(test.policy)
106+
g.Expect(conds).To(Equal(test.expConditions))
107+
})
108+
}
109+
}
110+
111+
func TestValidator_ValidateGlobalSettings(t *testing.T) {
112+
t.Parallel()
113+
g := NewWithT(t)
114+
115+
v := snippetspolicy.NewValidator()
116+
117+
g.Expect(v.ValidateGlobalSettings(nil, nil)).To(BeNil())
118+
}
119+
120+
func TestValidator_Conflicts(t *testing.T) {
121+
t.Parallel()
122+
g := NewWithT(t)
123+
124+
v := snippetspolicy.NewValidator()
125+
126+
g.Expect(v.Conflicts(nil, nil)).To(BeFalse())
127+
}

0 commit comments

Comments
 (0)