mirror of
https://github.com/lukaszraczylo/kubemirror.git
synced 2026-07-03 01:54:45 +00:00
Fix transformer handling logic and improve content hashing
This commit is contained in:
@@ -84,6 +84,14 @@ const (
|
|||||||
// AnnotationDeletionAttempts tracks number of failed deletion attempts.
|
// AnnotationDeletionAttempts tracks number of failed deletion attempts.
|
||||||
AnnotationDeletionAttempts = Domain + "/deletion-attempts"
|
AnnotationDeletionAttempts = Domain + "/deletion-attempts"
|
||||||
|
|
||||||
|
// Transformation Annotations
|
||||||
|
|
||||||
|
// AnnotationTransform contains YAML transformation rules for mirrored resources.
|
||||||
|
AnnotationTransform = Domain + "/transform"
|
||||||
|
|
||||||
|
// AnnotationTransformStrict enables strict mode (transformation errors block mirroring).
|
||||||
|
AnnotationTransformStrict = Domain + "/transform-strict"
|
||||||
|
|
||||||
// Finalizers
|
// Finalizers
|
||||||
|
|
||||||
// FinalizerName is the finalizer added to source resources.
|
// FinalizerName is the finalizer added to source resources.
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ func applyTransformations(source, mirror runtime.Object, targetNamespace string)
|
|||||||
return mirror, nil
|
return mirror, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transformRules, hasTransform := sourceAnnotations[transformer.AnnotationTransform]
|
transformRules, hasTransform := sourceAnnotations[constants.AnnotationTransform]
|
||||||
if !hasTransform || transformRules == "" {
|
if !hasTransform || transformRules == "" {
|
||||||
return mirror, nil // No transformation rules
|
return mirror, nil // No transformation rules
|
||||||
}
|
}
|
||||||
@@ -422,9 +422,9 @@ func applyTransformations(source, mirror runtime.Object, targetNamespace string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy transform annotations from source
|
// Copy transform annotations from source
|
||||||
mirrorAnnotations[transformer.AnnotationTransform] = transformRules
|
mirrorAnnotations[constants.AnnotationTransform] = transformRules
|
||||||
if strictMode, hasStrict := sourceAnnotations[transformer.AnnotationTransformStrict]; hasStrict {
|
if strictMode, hasStrict := sourceAnnotations[constants.AnnotationTransformStrict]; hasStrict {
|
||||||
mirrorAnnotations[transformer.AnnotationTransformStrict] = strictMode
|
mirrorAnnotations[constants.AnnotationTransformStrict] = strictMode
|
||||||
}
|
}
|
||||||
mirrorObj.SetAnnotations(mirrorAnnotations)
|
mirrorObj.SetAnnotations(mirrorAnnotations)
|
||||||
|
|
||||||
@@ -443,8 +443,8 @@ func applyTransformations(source, mirror runtime.Object, targetNamespace string)
|
|||||||
// Remove transform annotations from result (they shouldn't persist on mirrors)
|
// Remove transform annotations from result (they shouldn't persist on mirrors)
|
||||||
if transformedObj, ok := transformed.(metav1.Object); ok {
|
if transformedObj, ok := transformed.(metav1.Object); ok {
|
||||||
annotations := transformedObj.GetAnnotations()
|
annotations := transformedObj.GetAnnotations()
|
||||||
delete(annotations, transformer.AnnotationTransform)
|
delete(annotations, constants.AnnotationTransform)
|
||||||
delete(annotations, transformer.AnnotationTransformStrict)
|
delete(annotations, constants.AnnotationTransformStrict)
|
||||||
transformedObj.SetAnnotations(annotations)
|
transformedObj.SetAnnotations(annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-2
@@ -51,19 +51,37 @@ func extractContent(obj runtime.Object) (interface{}, error) {
|
|||||||
|
|
||||||
// extractSecretContent extracts content from a Secret.
|
// extractSecretContent extracts content from a Secret.
|
||||||
func extractSecretContent(secret *corev1.Secret) map[string]interface{} {
|
func extractSecretContent(secret *corev1.Secret) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
content := map[string]interface{}{
|
||||||
"type": string(secret.Type),
|
"type": string(secret.Type),
|
||||||
"data": secret.Data,
|
"data": secret.Data,
|
||||||
"stringData": secret.StringData,
|
"stringData": secret.StringData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include transform annotation in hash so changes to transformation rules trigger updates
|
||||||
|
if secret.Annotations != nil {
|
||||||
|
if transform, exists := secret.Annotations[constants.AnnotationTransform]; exists {
|
||||||
|
content["transform"] = transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractConfigMapContent extracts content from a ConfigMap.
|
// extractConfigMapContent extracts content from a ConfigMap.
|
||||||
func extractConfigMapContent(cm *corev1.ConfigMap) map[string]interface{} {
|
func extractConfigMapContent(cm *corev1.ConfigMap) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
content := map[string]interface{}{
|
||||||
"data": cm.Data,
|
"data": cm.Data,
|
||||||
"binaryData": cm.BinaryData,
|
"binaryData": cm.BinaryData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include transform annotation in hash so changes to transformation rules trigger updates
|
||||||
|
if cm.Annotations != nil {
|
||||||
|
if transform, exists := cm.Annotations[constants.AnnotationTransform]; exists {
|
||||||
|
content["transform"] = transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractUnstructuredContent extracts content from an unstructured resource (CRDs, etc.).
|
// extractUnstructuredContent extracts content from an unstructured resource (CRDs, etc.).
|
||||||
@@ -100,6 +118,14 @@ func extractUnstructuredContent(obj runtime.Object) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include transform annotation in hash so changes to transformation rules trigger updates
|
||||||
|
annotations := uCopy.GetAnnotations()
|
||||||
|
if annotations != nil {
|
||||||
|
if transform, exists := annotations[constants.AnnotationTransform]; exists {
|
||||||
|
content["transform"] = transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,14 +11,8 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
"github.com/lukaszraczylo/kubemirror/pkg/constants"
|
||||||
// AnnotationTransform is the annotation key for transformation rules
|
|
||||||
AnnotationTransform = "kubemirror.raczylo.com/transform"
|
|
||||||
|
|
||||||
// AnnotationTransformStrict enables strict mode (errors block mirroring)
|
|
||||||
AnnotationTransformStrict = "kubemirror.raczylo.com/transform-strict"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transformer applies transformation rules to Kubernetes resources.
|
// Transformer applies transformation rules to Kubernetes resources.
|
||||||
@@ -93,7 +87,7 @@ func (t *Transformer) parseTransformRules(u *unstructured.Unstructured) (*Transf
|
|||||||
return &TransformRules{}, nil
|
return &TransformRules{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rulesYAML, exists := annotations[AnnotationTransform]
|
rulesYAML, exists := annotations[constants.AnnotationTransform]
|
||||||
if !exists || rulesYAML == "" {
|
if !exists || rulesYAML == "" {
|
||||||
return &TransformRules{}, nil
|
return &TransformRules{}, nil
|
||||||
}
|
}
|
||||||
@@ -256,7 +250,7 @@ func (t *Transformer) isStrictMode(u *unstructured.Unstructured) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
strictValue, exists := annotations[AnnotationTransformStrict]
|
strictValue, exists := annotations[constants.AnnotationTransformStrict]
|
||||||
return exists && (strictValue == "true" || strictValue == "1")
|
return exists && (strictValue == "true" || strictValue == "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lukaszraczylo/kubemirror/pkg/constants"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -30,7 +31,7 @@ func TestTransformer_Transform(t *testing.T) {
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: data.LOG_LEVEL
|
- path: data.LOG_LEVEL
|
||||||
value: "error"
|
value: "error"
|
||||||
@@ -63,7 +64,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: data.API_URL
|
- path: data.API_URL
|
||||||
template: "https://{{.TargetNamespace}}.api.example.com"
|
template: "https://{{.TargetNamespace}}.api.example.com"
|
||||||
@@ -93,7 +94,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: data.NAMESPACE_UPPER
|
- path: data.NAMESPACE_UPPER
|
||||||
template: "{{upper .TargetNamespace}}"
|
template: "{{upper .TargetNamespace}}"
|
||||||
@@ -136,7 +137,7 @@ rules:
|
|||||||
"app": "myapp",
|
"app": "myapp",
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: metadata.labels
|
- path: metadata.labels
|
||||||
merge:
|
merge:
|
||||||
@@ -165,7 +166,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: metadata.labels
|
- path: metadata.labels
|
||||||
merge:
|
merge:
|
||||||
@@ -193,7 +194,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: data.DEBUG_MODE
|
- path: data.DEBUG_MODE
|
||||||
delete: true
|
delete: true
|
||||||
@@ -227,8 +228,8 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: "invalid: yaml: [[[",
|
constants.AnnotationTransform: "invalid: yaml: [[[",
|
||||||
AnnotationTransformStrict: "true",
|
constants.AnnotationTransformStrict: "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string]string{},
|
Data: map[string]string{},
|
||||||
@@ -247,11 +248,11 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- value: "something"
|
- value: "something"
|
||||||
`,
|
`,
|
||||||
AnnotationTransformStrict: "true",
|
constants.AnnotationTransformStrict: "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string]string{},
|
Data: map[string]string{},
|
||||||
@@ -273,8 +274,8 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: rules,
|
constants.AnnotationTransform: rules,
|
||||||
AnnotationTransformStrict: "true",
|
constants.AnnotationTransformStrict: "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string]string{},
|
Data: map[string]string{},
|
||||||
@@ -319,7 +320,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: "invalid yaml [[[",
|
constants.AnnotationTransform: "invalid yaml [[[",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
@@ -347,7 +348,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: data.KEY1
|
- path: data.KEY1
|
||||||
value: "first"
|
value: "first"
|
||||||
@@ -402,7 +403,7 @@ rules:
|
|||||||
"name": "test-pod",
|
"name": "test-pod",
|
||||||
"namespace": "default",
|
"namespace": "default",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: spec.containers[0].image
|
- path: spec.containers[0].image
|
||||||
template: "registry.{{.TargetNamespace}}.example.com/app:v1"
|
template: "registry.{{.TargetNamespace}}.example.com/app:v1"
|
||||||
@@ -457,7 +458,7 @@ rules:
|
|||||||
Name: "test-config",
|
Name: "test-config",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
AnnotationTransform: `
|
constants.AnnotationTransform: `
|
||||||
rules:
|
rules:
|
||||||
- path: data.VALUE
|
- path: data.VALUE
|
||||||
template: "{{.TargetNamespace}}-empty"
|
template: "{{.TargetNamespace}}-empty"
|
||||||
@@ -1336,7 +1337,7 @@ rules:
|
|||||||
"name": "test-config",
|
"name": "test-config",
|
||||||
"namespace": "source-namespace",
|
"namespace": "source-namespace",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
AnnotationTransform: tt.rules,
|
constants.AnnotationTransform: tt.rules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user