-
Notifications
You must be signed in to change notification settings - Fork 245
fix(live): validate annotation values to prevent silent data loss #4371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix(live): validate annotation values to prevent silent data loss #4371
Conversation
Previously, invalid annotation types (like unquoted booleans) were silently dropped during apply. This adds a validation step to reject non-string values with a clear error message. Fixes kptdev#3784 Signed-off-by: Jaisheesh-2006 <[email protected]>
✅ Deploy Preview for kptdocs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements strict validation for metadata.annotations values during the live apply pipeline to prevent silent data loss. Previously, non-string annotation values (e.g., unquoted booleans or integers) were silently discarded by the Go unmarshaler, resulting in annotations being dropped without warning.
Changes:
- Adds annotation value type validation in the live apply pipeline that inspects raw YAML tags
- Introduces comprehensive unit tests for annotation validation logic
- Adds integration tests to existing test suites to ensure validation is enforced in both path and stream readers
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/live/rgpath.go | Implements validateAnnotationTypes and yamlTagToType functions, integrates validation into kyamlNodeToUnstructured |
| pkg/live/validate_annotation_test.go | Comprehensive unit tests covering valid strings, invalid types (boolean, integer, float, null), edge cases, and error messages |
| pkg/live/rgstream_test.go | Adds integration test case for bad annotation type rejection in stream reader |
| pkg/live/rgpath_test.go | Adds integration test case for bad annotation type rejection in path reader |
| pkg/live/helpers_test.go | Defines configMapWithBadAnnotation test fixture used by integration tests |
| e2e/testdata/live-apply/invalid-annotation-type/config.yaml | E2E test configuration for invalid annotation type scenario |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -109,6 +113,60 @@ func kyamlNodeToUnstructured(n *yaml.RNode) (*unstructured.Unstructured, error) | |||
| }, nil | |||
| } | |||
|
|
|||
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is missing a documentation comment. According to Go conventions and best practices visible throughout the codebase (e.g., kyamlNodeToUnstructured on line 92, removeAnnotations on line 81), exported and internal functions should have documentation comments that explain their purpose, parameters, and behavior. Add a comment explaining what this function does, what errors it returns, and when those errors occur.
| // validateAnnotationTypes verifies that the metadata.annotations field on the | |
| // provided RNode, if present and non-null, is a mapping node whose values are | |
| // all string scalars. It returns nil when metadata or annotations are missing | |
| // or explicitly null. It returns an error if metadata.annotations is not a | |
| // mapping node or if any annotation value is not a string. |
|
|
||
| return nil | ||
| } | ||
|
|
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper function is missing a documentation comment. According to Go conventions and best practices visible throughout the codebase, functions should have documentation comments that explain their purpose. Add a comment explaining that this function converts YAML type tags to human-readable type names for error messages.
| // yamlTagToType converts YAML type tags to human-readable type names for error messages. |
pkg/live/rgpath.go
Outdated
| func validateAnnotationTypes(n *yaml.RNode) error { | ||
| metadata := n.Field("metadata") | ||
| if metadata == nil || metadata.Value == nil { | ||
| return nil | ||
| } | ||
|
|
||
| annotations := metadata.Value.Field("annotations") | ||
| if annotations == nil || annotations.Value == nil { | ||
| return nil | ||
| } | ||
|
|
||
| annotationNode := annotations.Value.YNode() | ||
| // Handle explicit null annotations (annotations: null) | ||
| if annotationNode.Tag == "!!null" { | ||
| return nil | ||
| } | ||
| if annotationNode.Kind != yaml.MappingNode { | ||
| return fmt.Errorf("metadata.annotations must be a string map") | ||
| } | ||
|
|
||
| for i := 0; i < len(annotationNode.Content); i += 2 { | ||
| keyNode := annotationNode.Content[i] | ||
| valueNode := annotationNode.Content[i+1] | ||
| if valueNode.Kind != yaml.ScalarNode || valueNode.Tag != "!!str" { | ||
| return fmt.Errorf("annotation %q must be a string, got %s", keyNode.Value, yamlTagToType(valueNode)) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation logic only checks annotations but not labels. According to Kubernetes specifications, metadata.labels also must be a map of strings to strings, and they suffer from the same silent data loss issue when non-string values are used. Consider extending the validation to also check labels to prevent the same problem. You could either add label validation to this function or create a separate validateLabelTypes function and call it from kyamlNodeToUnstructured.
|
|
||
| # Error should be in stderr with clear message about which field failed | ||
| stdErr: | | ||
| annotation "example.com/enabled" must be a string, got boolean |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This e2e test configuration is incomplete. The test expects a "resources" subdirectory containing the actual resource manifests to test, but this directory is missing from the PR. Based on the test runner implementation (pkg/test/live/runner.go line 79), the kpt command will be executed in the "resources" subdirectory of this test case. Without the resources directory containing a manifest with "example.com/enabled: true" annotation, this test will fail to run correctly. You should add a resources directory with a Kptfile and a resource manifest that has the problematic annotation (e.g., a ConfigMap with "example.com/enabled: true").
| annotation "example.com/enabled" must be a string, got boolean | |
| annotation "example.com/enabled" must be a string, got boolean | |
| # NOTE: The resources that trigger this error are expected to be in the | |
| # "resources" subdirectory of this test case, including a Kptfile and a | |
| # manifest (e.g., a ConfigMap) with annotation "example.com/enabled: true". |
pkg/live/rgpath.go
Outdated
| return nil | ||
| } | ||
|
|
||
| annotationNode := annotations.Value.YNode() |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a potential nil pointer dereference here. The YNode() method could return nil, but the code immediately accesses annotationNode.Tag on line 129 without checking if annotationNode is nil. While the check on line 123 ensures annotations.Value is not nil, you should add a nil check for annotationNode before accessing its fields to prevent potential panics.
| annotationNode := annotations.Value.YNode() | |
| annotationNode := annotations.Value.YNode() | |
| if annotationNode == nil { | |
| return nil | |
| } |
Signed-off-by: Jaisheesh-2006 <[email protected]>
5d17292 to
a0b112d
Compare
- Rename validateAnnotationTypes to validateMetadataStringMaps - Add validateStringMap helper function for reusability - Validate both annotations AND labels for non-string types - Add proper GoDoc comments to all functions - Create comprehensive test file validate_metadata_test.go - Fix E2E test expected error message prefix Fixes kptdev#3784 Signed-off-by: Jaisheesh-2006 <[email protected]>
Add resourcegroup.yaml to migrate from Kptfile-based inventory to ResourceGroup-based inventory, eliminating the warning message about using Kptfile for inventory information. Signed-off-by: Jaisheesh-2006 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Remove the inventory section from Kptfile since ResourceGroup is now used for inventory. Having both causes an inventory conflict error. Signed-off-by: Jaisheesh-2006 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mozesl-nokia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should probably be an E2E test added for the label validation as well.
| keyNode := mapNode.Content[i] | ||
| valueNode := mapNode.Content[i+1] | ||
| if valueNode.Kind != yaml.ScalarNode || valueNode.Tag != "!!str" { | ||
| return fmt.Errorf("%s %q must be a string, got %s", fieldType, keyNode.Value, yamlTagToType(valueNode)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need to convert the type to more human readable, but curious about the others' opinion.
Maybe separate the checks to kind != scalar and tag != !!str.
Also, is "!!str" not available as a const from somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mozesl-nokia , indeed !!str is available as a constant in yaml.NodeTagString, I will shortly replace the other ones as well and push it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be a test for labels here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mozesl-nokia , i will add the test for labels and commit it soon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be a test for labels here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be a test for labels here too?
Signed-off-by: Jaisheesh-2006 <[email protected]>
Description
This PR implements strict validation for
metadata.annotationsvalues during thelive applypipeline.Previously, the Go unmarshaler silently discarded non-string values (e.g., unquoted booleans like
trueor integers like123) because they did not match the expectedmap[string]stringschema. This resulted in annotations being dropped from the applied resource without warning.This change adds a validation step that inspects the raw YAML tags of
RNodes. It now returns a strict error if any annotation value is not a string, while correctly accepting quoted strings (e.g.,"true").Motivation
To prevent silent data loss and configuration drift.
Users frequently mistake YAML types (e.g., writing
enabled: trueinstead ofenabled: "true"). Currently,kptignores these fields silently. This fix ensures the user receives immediate feedback:New Error Output:
Error: annotation "example.com/enabled" must be a string, got booleanWhich issue does this PR fix?
Fixes #3784