Skip to content

Commit 264cf24

Browse files
authored
INS-1699: insights is overriding Kyverno policies annotations (#294)
* INS-1699: insights is overriding Kyverno policies annotations * INS-1699: insights is overriding Kyverno policies annotations * INS-1699: insights is overriding Kyverno policies annotations * INS-1699: insights is overriding Kyverno policies annotations
1 parent d7493ca commit 264cf24

File tree

3 files changed

+262
-20
lines changed

3 files changed

+262
-20
lines changed

pkg/kyverno/kyverno.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,20 +290,54 @@ func readPolicyFromFile(filePath string) (KyvernoPolicy, error) {
290290
return KyvernoPolicy{}, fmt.Errorf("failed to parse YAML in policy file %s: %w", filePath, err)
291291
}
292292

293-
// Extract name from metadata if not set
294-
if policy.Name == "" {
295-
// Parse YAML to extract metadata.name
296-
var yamlData map[string]interface{}
297-
err = yaml.Unmarshal(fileContents, &yamlData)
298-
if err != nil {
299-
return KyvernoPolicy{}, fmt.Errorf("failed to parse YAML metadata in policy file %s: %w", filePath, err)
293+
var yamlData map[string]interface{}
294+
err = yaml.Unmarshal(fileContents, &yamlData)
295+
if err != nil {
296+
return KyvernoPolicy{}, fmt.Errorf("failed to parse YAML metadata in policy file %s: %w", filePath, err)
297+
}
298+
299+
if metadata, ok := yamlData["metadata"].(map[string]interface{}); ok {
300+
// Store the full metadata object to preserve any additional fields
301+
policy.Metadata = make(map[string]any)
302+
for k, v := range metadata {
303+
policy.Metadata[k] = v
300304
}
301305

302-
if metadata, ok := yamlData["metadata"].(map[string]interface{}); ok {
306+
// Extract name if not set
307+
if policy.Name == "" {
303308
if name, ok := metadata["name"].(string); ok {
304309
policy.Name = name
305310
}
306311
}
312+
313+
// Extract namespace if present
314+
if namespace, ok := metadata["namespace"].(string); ok {
315+
policy.Namespace = namespace
316+
}
317+
318+
// Extract labels if present
319+
if labels, ok := metadata["labels"].(map[string]interface{}); ok {
320+
policy.Labels = make(map[string]any)
321+
for k, v := range labels {
322+
policy.Labels[k] = v
323+
}
324+
}
325+
326+
// Extract annotations if present
327+
if annotations, ok := metadata["annotations"].(map[string]interface{}); ok {
328+
policy.Annotations = make(map[string]any)
329+
for k, v := range annotations {
330+
policy.Annotations[k] = v
331+
}
332+
}
333+
}
334+
335+
// Extract status if present (at the top level)
336+
if status, ok := yamlData["status"].(map[string]interface{}); ok {
337+
policy.Status = make(map[string]any)
338+
for k, v := range status {
339+
policy.Status[k] = v
340+
}
307341
}
308342

309343
return policy, nil

pkg/kyverno/kyverno_test.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package kyverno
1616

1717
import (
18+
"os"
19+
"path/filepath"
1820
"testing"
1921

2022
"github.com/stretchr/testify/assert"
@@ -201,3 +203,185 @@ func TestConvertPolicySpecToYAML(t *testing.T) {
201203
assert.NoError(t, err)
202204
assert.Equal(t, "apiVersion: kyverno.io/v1\nkind: ClusterPolicy\nmetadata:\n name: test-policy\nspec:\n metadata:\n name: test-policy", yaml)
203205
}
206+
207+
func TestReadPolicyFromFileWithLabelsAndAnnotations(t *testing.T) {
208+
// Create a temporary directory and file for testing
209+
tmpDir := t.TempDir()
210+
policyContent := `apiVersion: kyverno.io/v1
211+
kind: ClusterPolicy
212+
metadata:
213+
name: policy-with-metadata
214+
labels:
215+
app: my-app
216+
environment: production
217+
team: security
218+
annotations:
219+
description: "This is a test policy with labels and annotations"
220+
owner: platform-team
221+
spec:
222+
validationFailureAction: enforce
223+
background: true
224+
rules:
225+
- name: check-labels
226+
match:
227+
any:
228+
- resources:
229+
kinds:
230+
- Pod
231+
validate:
232+
message: "Labels are required"
233+
pattern:
234+
metadata:
235+
labels:
236+
app: "?*"
237+
`
238+
policyFile := filepath.Join(tmpDir, "policy-with-metadata.yaml")
239+
err := os.WriteFile(policyFile, []byte(policyContent), 0644)
240+
assert.NoError(t, err)
241+
242+
policy, err := readPolicyFromFile(policyFile)
243+
assert.NoError(t, err)
244+
245+
// Verify basic fields
246+
assert.Equal(t, "policy-with-metadata", policy.Name)
247+
assert.Equal(t, "ClusterPolicy", policy.Kind)
248+
assert.Equal(t, "kyverno.io/v1", policy.APIVersion)
249+
250+
// Verify labels are extracted
251+
assert.NotNil(t, policy.Labels)
252+
assert.Equal(t, "my-app", policy.Labels["app"])
253+
assert.Equal(t, "production", policy.Labels["environment"])
254+
assert.Equal(t, "security", policy.Labels["team"])
255+
256+
// Verify annotations are extracted
257+
assert.NotNil(t, policy.Annotations)
258+
assert.Equal(t, "This is a test policy with labels and annotations", policy.Annotations["description"])
259+
assert.Equal(t, "platform-team", policy.Annotations["owner"])
260+
261+
// Verify spec is extracted
262+
assert.NotNil(t, policy.Spec)
263+
}
264+
265+
func TestReadPolicyFromFileWithoutLabelsAndAnnotations(t *testing.T) {
266+
policy, err := readPolicyFromFile("testdata/disallow-privileged.yaml")
267+
assert.NoError(t, err)
268+
269+
// Verify basic fields
270+
assert.Equal(t, "disallow-privileged", policy.Name)
271+
assert.Equal(t, "Policy", policy.Kind)
272+
assert.Equal(t, "kyverno.io/v1", policy.APIVersion)
273+
274+
// Labels and annotations should be nil or empty
275+
assert.Empty(t, policy.Labels)
276+
assert.Empty(t, policy.Annotations)
277+
278+
// Verify spec is extracted
279+
assert.NotNil(t, policy.Spec)
280+
}
281+
282+
func TestReadPolicyFromFileWithNamespaceAndFullMetadata(t *testing.T) {
283+
// Create a temporary directory and file for testing
284+
tmpDir := t.TempDir()
285+
policyContent := `apiVersion: kyverno.io/v1
286+
kind: Policy
287+
metadata:
288+
name: namespaced-policy
289+
namespace: my-namespace
290+
labels:
291+
app: test-app
292+
annotations:
293+
description: "A namespaced policy"
294+
generateName: test-prefix-
295+
finalizers:
296+
- kyverno.io/finalizer
297+
spec:
298+
validationFailureAction: enforce
299+
background: true
300+
rules:
301+
- name: check-labels
302+
match:
303+
any:
304+
- resources:
305+
kinds:
306+
- Pod
307+
validate:
308+
message: "Labels are required"
309+
pattern:
310+
metadata:
311+
labels:
312+
app: "?*"
313+
`
314+
policyFile := filepath.Join(tmpDir, "namespaced-policy.yaml")
315+
err := os.WriteFile(policyFile, []byte(policyContent), 0644)
316+
assert.NoError(t, err)
317+
318+
policy, err := readPolicyFromFile(policyFile)
319+
assert.NoError(t, err)
320+
321+
// Verify basic fields
322+
assert.Equal(t, "namespaced-policy", policy.Name)
323+
assert.Equal(t, "Policy", policy.Kind)
324+
assert.Equal(t, "kyverno.io/v1", policy.APIVersion)
325+
326+
// Verify namespace is extracted
327+
assert.Equal(t, "my-namespace", policy.Namespace)
328+
329+
// Verify labels are extracted
330+
assert.NotNil(t, policy.Labels)
331+
assert.Equal(t, "test-app", policy.Labels["app"])
332+
333+
// Verify annotations are extracted
334+
assert.NotNil(t, policy.Annotations)
335+
assert.Equal(t, "A namespaced policy", policy.Annotations["description"])
336+
337+
// Verify full metadata is preserved (including generateName, finalizers, etc.)
338+
assert.NotNil(t, policy.Metadata)
339+
assert.Equal(t, "namespaced-policy", policy.Metadata["name"])
340+
assert.Equal(t, "my-namespace", policy.Metadata["namespace"])
341+
assert.Equal(t, "test-prefix-", policy.Metadata["generateName"])
342+
343+
// Check finalizers are preserved
344+
finalizers, ok := policy.Metadata["finalizers"].([]interface{})
345+
assert.True(t, ok, "finalizers should be a slice")
346+
assert.Len(t, finalizers, 1)
347+
assert.Equal(t, "kyverno.io/finalizer", finalizers[0])
348+
349+
// Verify spec is extracted
350+
assert.NotNil(t, policy.Spec)
351+
}
352+
353+
func TestToKyvernoPolicyInputPreservesAllFields(t *testing.T) {
354+
policy := KyvernoPolicy{
355+
Name: "test-policy",
356+
Kind: "Policy",
357+
APIVersion: "kyverno.io/v1",
358+
Namespace: "test-namespace",
359+
Labels: map[string]any{
360+
"app": "my-app",
361+
},
362+
Annotations: map[string]any{
363+
"description": "Test policy",
364+
},
365+
Metadata: map[string]any{
366+
"name": "test-policy",
367+
"namespace": "test-namespace",
368+
"generateName": "prefix-",
369+
},
370+
Spec: map[string]any{
371+
"validationFailureAction": "enforce",
372+
},
373+
}
374+
375+
input := policy.ToKyvernoPolicyInput()
376+
377+
// Verify all fields are preserved
378+
assert.Equal(t, "test-policy", input.Name)
379+
assert.Equal(t, "Policy", input.Kind)
380+
assert.Equal(t, "kyverno.io/v1", input.APIVersion)
381+
assert.Equal(t, "test-namespace", input.Namespace)
382+
assert.Equal(t, "my-app", input.Labels["app"])
383+
assert.Equal(t, "Test policy", input.Annotations["description"])
384+
assert.NotNil(t, input.Metadata)
385+
assert.Equal(t, "prefix-", input.Metadata["generateName"])
386+
assert.NotNil(t, input.Spec)
387+
}

pkg/kyverno/types.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ type KyvernoPolicy struct {
2828
Name string `json:"name" yaml:"metadata.name"`
2929
Kind string `json:"kind" yaml:"kind"`
3030
APIVersion string `json:"apiVersion" yaml:"apiVersion"`
31+
Namespace string `json:"namespace,omitempty" yaml:"metadata.namespace"`
3132
Labels map[string]any `json:"labels,omitempty" yaml:"metadata.labels"`
3233
Annotations map[string]any `json:"annotations,omitempty" yaml:"metadata.annotations"`
34+
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata"` // Full metadata for any additional fields
3335
Spec map[string]any `json:"spec" yaml:"spec"`
3436
Status map[string]any `json:"status,omitempty"`
3537
ManagedByInsights *bool `json:"managedByInsights,omitempty" yaml:"managedByInsights,omitempty"`
@@ -47,24 +49,42 @@ func (k KyvernoPolicy) GetYamlBytes() ([]byte, error) {
4749

4850
// Helper function to convert a KyvernoPolicy spec to YAML string
4951
func convertPolicySpecToYAML(policy KyvernoPolicy) (string, error) {
50-
// Create the full policy structure
51-
policyMap := map[string]any{
52-
"apiVersion": policy.APIVersion,
53-
"kind": policy.Kind,
54-
"metadata": map[string]any{
55-
"name": policy.Name,
56-
},
57-
"spec": policy.Spec,
52+
// Start with the full metadata if available, otherwise create a new map
53+
var metadata map[string]any
54+
if len(policy.Metadata) > 0 {
55+
// Deep copy the metadata to avoid modifying the original
56+
metadata = make(map[string]any)
57+
for k, v := range policy.Metadata {
58+
metadata[k] = v
59+
}
60+
} else {
61+
metadata = make(map[string]any)
62+
}
63+
64+
// Ensure name is set in metadata
65+
metadata["name"] = policy.Name
66+
67+
// Add namespace if set
68+
if policy.Namespace != "" {
69+
metadata["namespace"] = policy.Namespace
5870
}
5971

60-
// Add labels and annotations if they exist and are not empty
72+
// Add labels if they exist and are not empty
6173
if len(policy.Labels) > 0 {
62-
policyMap["metadata"].(map[string]any)["labels"] = policy.Labels
74+
metadata["labels"] = policy.Labels
6375
}
6476

65-
// Add existing annotations if they exist
77+
// Add annotations if they exist
6678
if len(policy.Annotations) > 0 {
67-
policyMap["metadata"].(map[string]any)["annotations"] = policy.Annotations
79+
metadata["annotations"] = policy.Annotations
80+
}
81+
82+
// Create the full policy structure
83+
policyMap := map[string]any{
84+
"apiVersion": policy.APIVersion,
85+
"kind": policy.Kind,
86+
"metadata": metadata,
87+
"spec": policy.Spec,
6888
}
6989

7090
// Add status only if it exists and is not null
@@ -181,8 +201,10 @@ type KyvernoPolicyInput struct {
181201
Name string `json:"name"`
182202
Kind string `json:"kind"`
183203
APIVersion string `json:"apiVersion"`
204+
Namespace string `json:"namespace,omitempty"`
184205
Labels map[string]string `json:"labels,omitempty"`
185206
Annotations map[string]string `json:"annotations,omitempty"`
207+
Metadata map[string]any `json:"metadata,omitempty"` // Full metadata for any additional fields
186208
Spec map[string]any `json:"spec"`
187209
Status *map[string]any `json:"status,omitempty"`
188210
ManagedByInsights *bool `json:"managedByInsights,omitempty"`
@@ -222,8 +244,10 @@ func (k KyvernoPolicy) ToKyvernoPolicyInput() KyvernoPolicyInput {
222244
Name: k.Name,
223245
Kind: k.Kind,
224246
APIVersion: k.APIVersion,
247+
Namespace: k.Namespace,
225248
Labels: labels,
226249
Annotations: annotations,
250+
Metadata: k.Metadata,
227251
Spec: k.Spec,
228252
Status: status,
229253
ManagedByInsights: k.ManagedByInsights,

0 commit comments

Comments
 (0)