Skip to content

Commit f47f54e

Browse files
committed
Add support for multiple target references in SnippetsPolicy
1 parent 63897e7 commit f47f54e

File tree

15 files changed

+200
-124
lines changed

15 files changed

+200
-124
lines changed

apis/v1alpha1/policy_methods.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (p *UpstreamSettingsPolicy) SetPolicyStatus(status gatewayv1.PolicyStatus)
3333
}
3434

3535
func (p *SnippetsPolicy) GetTargetRefs() []gatewayv1.LocalPolicyTargetReference {
36-
return []gatewayv1.LocalPolicyTargetReference{p.Spec.TargetRef.LocalPolicyTargetReference}
36+
return p.Spec.TargetRefs
3737
}
3838

3939
func (p *SnippetsPolicy) GetPolicyStatus() gatewayv1.PolicyStatus {

apis/v1alpha1/snippetspolicy_types.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ import (
2525
// +kubebuilder:object:root=true
2626
// +kubebuilder:storageversion
2727
// +kubebuilder:subresource:status
28-
// +kubebuilder:resource:shortName=snipol
28+
// +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=direct"
29+
// +kubebuilder:resource:categories=nginx-gateway-fabric,shortName=snippetspolicy
2930
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
30-
// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`
31-
// +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].reason`
3231

3332
// SnippetsPolicy provides a way to inject NGINX snippets into the configuration on Gateway level.
3433
type SnippetsPolicy struct {
@@ -53,11 +52,13 @@ type SnippetsPolicyList struct {
5352

5453
// SnippetsPolicySpec defines the desired state of the SnippetsPolicy.
5554
type SnippetsPolicySpec struct {
56-
// TargetRef is the reference to the Gateway that this policy should be applied to.
57-
// +kubebuilder:validation:XValidation:message="TargetRef Kind must be Gateway",rule="self.kind == 'Gateway'"
58-
// +kubebuilder:validation:XValidation:message="TargetRef Group must be gateway.networking.k8s.io",rule="self.group == 'gateway.networking.k8s.io'"
55+
// TargetRefs identifies API object(s) to apply the policy to.
56+
// +kubebuilder:validation:MinItems=1
57+
// +kubebuilder:validation:MaxItems=16
58+
// +kubebuilder:validation:XValidation:message="TargetRefs Kind must be Gateway",rule="self.all(t, t.kind == 'Gateway')"
59+
// +kubebuilder:validation:XValidation:message="TargetRefs Group must be gateway.networking.k8s.io",rule="self.all(t, t.group == 'gateway.networking.k8s.io')"
5960
//nolint:lll
60-
TargetRef SnippetsPolicyTargetRef `json:"targetRef"`
61+
TargetRefs []gatewayv1.LocalPolicyTargetReference `json:"targetRefs"`
6162

6263
// Snippets is a list of snippets to be injected into the NGINX configuration.
6364
// +kubebuilder:validation:MaxItems=3
@@ -66,8 +67,3 @@ type SnippetsPolicySpec struct {
6667
//nolint:lll
6768
Snippets []Snippet `json:"snippets"`
6869
}
69-
70-
// SnippetsPolicyTargetRef identifies an API object to apply the policy to.
71-
type SnippetsPolicyTargetRef struct {
72-
gatewayv1.LocalPolicyTargetReference `json:",inline"`
73-
}

apis/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/gateway.nginx.org_snippetspolicies.yaml

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,26 @@ kind: CustomResourceDefinition
44
metadata:
55
annotations:
66
controller-gen.kubebuilder.io/version: v0.19.0
7+
labels:
8+
gateway.networking.k8s.io/policy: direct
79
name: snippetspolicies.gateway.nginx.org
810
spec:
911
group: gateway.nginx.org
1012
names:
13+
categories:
14+
- nginx-gateway-fabric
1115
kind: SnippetsPolicy
1216
listKind: SnippetsPolicyList
1317
plural: snippetspolicies
1418
shortNames:
15-
- snipol
19+
- snippetspolicy
1620
singular: snippetspolicy
1721
scope: Namespaced
1822
versions:
1923
- additionalPrinterColumns:
2024
- jsonPath: .metadata.creationTimestamp
2125
name: Age
2226
type: date
23-
- jsonPath: .status.conditions[?(@.type=="Accepted")].status
24-
name: Accepted
25-
type: string
26-
- jsonPath: .status.conditions[?(@.type=="Accepted")].reason
27-
name: Reason
28-
type: string
2927
name: v1alpha1
3028
schema:
3129
openAPIV3Schema:
@@ -82,39 +80,49 @@ spec:
8280
rule: self.all(s1, self.exists_one(s2, s1.context == s2.context))
8381
- message: http.server.location context is not supported in SnippetsPolicy
8482
rule: '!self.exists(s, s.context == ''http.server.location'')'
85-
targetRef:
86-
description: TargetRef is the reference to the Gateway that this policy
87-
should be applied to.
88-
properties:
89-
group:
90-
description: Group is the group of the target resource.
91-
maxLength: 253
92-
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
93-
type: string
94-
kind:
95-
description: Kind is kind of the target resource.
96-
maxLength: 63
97-
minLength: 1
98-
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
99-
type: string
100-
name:
101-
description: Name is the name of the target resource.
102-
maxLength: 253
103-
minLength: 1
104-
type: string
105-
required:
106-
- group
107-
- kind
108-
- name
109-
type: object
83+
targetRefs:
84+
description: TargetRefs identifies API object(s) to apply the policy
85+
to.
86+
items:
87+
description: |-
88+
LocalPolicyTargetReference identifies an API object to apply a direct or
89+
inherited policy to. This should be used as part of Policy resources
90+
that can target Gateway API resources. For more information on how this
91+
policy attachment model works, and a sample Policy resource, refer to
92+
the policy attachment documentation for Gateway API.
93+
properties:
94+
group:
95+
description: Group is the group of the target resource.
96+
maxLength: 253
97+
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
98+
type: string
99+
kind:
100+
description: Kind is kind of the target resource.
101+
maxLength: 63
102+
minLength: 1
103+
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
104+
type: string
105+
name:
106+
description: Name is the name of the target resource.
107+
maxLength: 253
108+
minLength: 1
109+
type: string
110+
required:
111+
- group
112+
- kind
113+
- name
114+
type: object
115+
maxItems: 16
116+
minItems: 1
117+
type: array
110118
x-kubernetes-validations:
111-
- message: TargetRef Kind must be Gateway
112-
rule: self.kind == 'Gateway'
113-
- message: TargetRef Group must be gateway.networking.k8s.io
114-
rule: self.group == 'gateway.networking.k8s.io'
119+
- message: TargetRefs Kind must be Gateway
120+
rule: self.all(t, t.kind == 'Gateway')
121+
- message: TargetRefs Group must be gateway.networking.k8s.io
122+
rule: self.all(t, t.group == 'gateway.networking.k8s.io')
115123
required:
116124
- snippets
117-
- targetRef
125+
- targetRefs
118126
type: object
119127
status:
120128
description: Status defines the current state of the SnippetsPolicy.

internal/controller/nginx/config/policies/snippetspolicy/generator.go

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -79,41 +79,42 @@ func (g *Generator) generate(pols []policies.Policy, context v1alpha1.NginxConte
7979
continue
8080
}
8181

82-
// TargetRef info for file path and comment
83-
gwRef := sp.Spec.TargetRef
84-
gwNsName := fmt.Sprintf("%s-%s", sp.GetNamespace(), gwRef.Name) // Assuming local target ref implies same namespace
85-
86-
policyNsName := fmt.Sprintf("%s/%s", sp.GetNamespace(), sp.GetName())
87-
88-
// Build content and filename
89-
var content string
90-
var filename string
91-
92-
switch context {
93-
case v1alpha1.NginxContextMain:
94-
content = fmt.Sprintf(mainTemplate, policyNsName, gwNsName, snippet.Value)
95-
filename = filepath.Join(
96-
"includes", "policy", gwNsName,
97-
fmt.Sprintf("SnippetsPolicy_main_%s.conf", sp.GetName()),
98-
)
99-
case v1alpha1.NginxContextHTTP:
100-
content = fmt.Sprintf(httpTemplate, policyNsName, gwNsName, snippet.Value)
101-
filename = filepath.Join(
102-
"includes", "policy", gwNsName,
103-
fmt.Sprintf("SnippetsPolicy_http_%s.conf", sp.GetName()),
104-
)
105-
case v1alpha1.NginxContextHTTPServer:
106-
content = fmt.Sprintf(serverTemplate, policyNsName, gwNsName, serverID, snippet.Value)
107-
filename = filepath.Join(
108-
"includes", "policy", gwNsName, serverID,
109-
fmt.Sprintf("SnippetsPolicy_server_%s.conf", sp.GetName()),
110-
)
82+
for _, gwRef := range sp.Spec.TargetRefs {
83+
// TargetRef info for file path and comment
84+
gwNsName := fmt.Sprintf("%s-%s", sp.GetNamespace(), gwRef.Name) // Assuming local target ref implies same namespace
85+
86+
policyNsName := fmt.Sprintf("%s/%s", sp.GetNamespace(), sp.GetName())
87+
88+
// Build content and filename
89+
var content string
90+
var filename string
91+
92+
switch context {
93+
case v1alpha1.NginxContextMain:
94+
content = fmt.Sprintf(mainTemplate, policyNsName, gwNsName, snippet.Value)
95+
filename = filepath.Join(
96+
"includes", "policy", gwNsName,
97+
fmt.Sprintf("SnippetsPolicy_main_%s.conf", sp.GetName()),
98+
)
99+
case v1alpha1.NginxContextHTTP:
100+
content = fmt.Sprintf(httpTemplate, policyNsName, gwNsName, snippet.Value)
101+
filename = filepath.Join(
102+
"includes", "policy", gwNsName,
103+
fmt.Sprintf("SnippetsPolicy_http_%s.conf", sp.GetName()),
104+
)
105+
case v1alpha1.NginxContextHTTPServer:
106+
content = fmt.Sprintf(serverTemplate, policyNsName, gwNsName, serverID, snippet.Value)
107+
filename = filepath.Join(
108+
"includes", "policy", gwNsName, serverID,
109+
fmt.Sprintf("SnippetsPolicy_server_%s.conf", sp.GetName()),
110+
)
111+
}
112+
113+
files = append(files, policies.File{
114+
Name: filename,
115+
Content: []byte(content),
116+
})
111117
}
112-
113-
files = append(files, policies.File{
114-
Name: filename,
115-
Content: []byte(content),
116-
})
117118
}
118119
}
119120
return files

internal/controller/nginx/config/policies/snippetspolicy/generator_test.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ func TestGenerator(t *testing.T) {
2323
Namespace: "default",
2424
},
2525
Spec: v1alpha1.SnippetsPolicySpec{
26-
TargetRef: v1alpha1.SnippetsPolicyTargetRef{
27-
LocalPolicyTargetReference: gatewayv1.LocalPolicyTargetReference{
26+
TargetRefs: []gatewayv1.LocalPolicyTargetReference{
27+
{
2828
Group: gatewayv1.GroupName,
2929
Kind: kinds.Gateway,
3030
Name: "gateway-1",
@@ -81,8 +81,8 @@ func TestGenerator(t *testing.T) {
8181
policy1 := &v1alpha1.SnippetsPolicy{
8282
ObjectMeta: metav1.ObjectMeta{Name: "p1", Namespace: "default"},
8383
Spec: v1alpha1.SnippetsPolicySpec{
84-
TargetRef: v1alpha1.SnippetsPolicyTargetRef{
85-
LocalPolicyTargetReference: gatewayv1.LocalPolicyTargetReference{
84+
TargetRefs: []gatewayv1.LocalPolicyTargetReference{
85+
{
8686
Name: "gw",
8787
},
8888
},
@@ -94,8 +94,8 @@ func TestGenerator(t *testing.T) {
9494
policy2 := &v1alpha1.SnippetsPolicy{
9595
ObjectMeta: metav1.ObjectMeta{Name: "p2", Namespace: "default"},
9696
Spec: v1alpha1.SnippetsPolicySpec{
97-
TargetRef: v1alpha1.SnippetsPolicyTargetRef{
98-
LocalPolicyTargetReference: gatewayv1.LocalPolicyTargetReference{
97+
TargetRefs: []gatewayv1.LocalPolicyTargetReference{
98+
{
9999
Name: "gw",
100100
},
101101
},
@@ -110,4 +110,33 @@ func TestGenerator(t *testing.T) {
110110
gWithT.Expect(files[0].Name).To(ContainSubstring("p1"))
111111
gWithT.Expect(files[1].Name).To(ContainSubstring("p2"))
112112
})
113+
114+
t.Run("GenerateForMain with multiple targets", func(t *testing.T) {
115+
gWithT := NewWithT(t)
116+
policy := &v1alpha1.SnippetsPolicy{
117+
ObjectMeta: metav1.ObjectMeta{Name: "policy-multi", Namespace: "default"},
118+
Spec: v1alpha1.SnippetsPolicySpec{
119+
TargetRefs: []gatewayv1.LocalPolicyTargetReference{
120+
{
121+
Name: "gw1",
122+
},
123+
{
124+
Name: "gw2",
125+
},
126+
},
127+
Snippets: []v1alpha1.Snippet{
128+
{Context: v1alpha1.NginxContextMain, Value: "data;"},
129+
},
130+
},
131+
}
132+
133+
files := g.GenerateForMain([]policies.Policy{policy})
134+
gWithT.Expect(files).To(HaveLen(2))
135+
gWithT.Expect(files[0].Name).To(ContainSubstring("gw1"))
136+
gWithT.Expect(files[1].Name).To(ContainSubstring("gw2"))
137+
gWithT.Expect(string(files[0].Content)).To(ContainSubstring("data;"))
138+
gWithT.Expect(string(files[1].Content)).To(ContainSubstring("data;"))
139+
gWithT.Expect(string(files[0].Content)).To(ContainSubstring("gw1"))
140+
gWithT.Expect(string(files[1].Content)).To(ContainSubstring("gw2"))
141+
})
113142
}

internal/controller/nginx/config/policies/snippetspolicy/validator.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ func NewValidator() *Validator {
4242
func (v *Validator) Validate(policy policies.Policy) []conditions.Condition {
4343
sp := helpers.MustCastObject[*ngfAPI.SnippetsPolicy](policy)
4444

45-
targetRefPath := field.NewPath("spec").Child("targetRef")
45+
targetRefsPath := field.NewPath("spec").Child("targetRefs")
4646
supportedKinds := []gatewayv1.Kind{kinds.Gateway}
4747
supportedGroups := []gatewayv1.Group{gatewayv1.GroupName}
4848

4949
// Validate TargetRef
50-
if err := policies.ValidateTargetRef(sp.Spec.TargetRef.LocalPolicyTargetReference, targetRefPath, supportedGroups, supportedKinds); err != nil {
51-
return []conditions.Condition{conditions.NewPolicyInvalid(err.Error())}
50+
for i, targetRef := range sp.Spec.TargetRefs {
51+
if err := policies.ValidateTargetRef(targetRef, targetRefsPath.Index(i), supportedGroups, supportedKinds); err != nil {
52+
return []conditions.Condition{conditions.NewPolicyInvalid(err.Error())}
53+
}
5254
}
5355

5456
// Validate Snippets

0 commit comments

Comments
 (0)