Skip to content

Commit 455c40a

Browse files
kaovilaiclaude
andauthored
Add deprecation warning for PodAnnotations field (OADP-6454) (#1914)
- Added deprecation warning in ValidateDataProtectionCR when PodAnnotations is used - Warning guides users to migrate to configuration.velero.podConfig.annotations for Velero pods and configuration.nodeAgent.podConfig.annotations for NodeAgent pods - Added comprehensive tests for deprecation warning functionality - Validation continues to pass when deprecated field is used (non-blocking) - No automatic migration - users must manually update their configurations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <[email protected]>
1 parent 6705731 commit 455c40a

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

internal/controller/validator.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ func (r *DataProtectionApplicationReconciler) ValidateDataProtectionCR(log logr.
4040
return false, errors.New("DPA CR Velero configuration cannot be nil")
4141
}
4242

43+
// Check for deprecated PodAnnotations field and log warning
44+
if len(r.dpa.Spec.PodAnnotations) > 0 {
45+
deprecationWarning := "(Deprecation Warning) The 'podAnnotations' field is deprecated. " +
46+
"Please migrate to 'configuration.velero.podConfig.annotations' for Velero pods " +
47+
"and 'configuration.nodeAgent.podConfig.annotations' for NodeAgent pods."
48+
// V(-1) corresponds to the warn level
49+
log.V(-1).Info(deprecationWarning)
50+
r.EventRecorder.Event(r.dpa, corev1.EventTypeWarning, "DeprecationPodAnnotations", deprecationWarning)
51+
}
52+
4353
if r.dpa.Spec.Configuration.Velero.NoDefaultBackupLocation {
4454
if len(r.dpa.Spec.BackupLocations) != 0 {
4555
return false, errors.New("DPA CR Velero configuration cannot have backup locations if noDefaultBackupLocation is set")

internal/controller/validator_test.go

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,6 +2447,62 @@ func TestDPAReconciler_ValidateDataProtectionCR(t *testing.T) {
24472447
},
24482448
wantErr: false,
24492449
},
2450+
{
2451+
name: "[valid] DPA CR with deprecated PodAnnotations should pass validation but log warning",
2452+
dpa: &oadpv1alpha1.DataProtectionApplication{
2453+
ObjectMeta: metav1.ObjectMeta{
2454+
Name: "test-DPA-CR",
2455+
Namespace: "test-ns",
2456+
},
2457+
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
2458+
PodAnnotations: map[string]string{
2459+
"deprecated.annotation": "value",
2460+
},
2461+
BackupLocations: []oadpv1alpha1.BackupLocation{
2462+
{
2463+
Velero: &velerov1.BackupStorageLocationSpec{
2464+
Provider: "aws",
2465+
StorageType: velerov1.StorageType{
2466+
ObjectStorage: &velerov1.ObjectStorageLocation{
2467+
Bucket: "bucket",
2468+
},
2469+
},
2470+
Config: map[string]string{
2471+
"region": "us-east-1",
2472+
},
2473+
Credential: &corev1.SecretKeySelector{
2474+
LocalObjectReference: corev1.LocalObjectReference{
2475+
Name: "cloud-credentials",
2476+
},
2477+
Key: "credentials",
2478+
},
2479+
Default: true,
2480+
},
2481+
},
2482+
},
2483+
Configuration: &oadpv1alpha1.ApplicationConfig{
2484+
Velero: &oadpv1alpha1.VeleroConfig{
2485+
DefaultPlugins: []oadpv1alpha1.DefaultPlugin{
2486+
oadpv1alpha1.DefaultPluginAWS,
2487+
},
2488+
},
2489+
},
2490+
BackupImages: ptr.To(false),
2491+
},
2492+
},
2493+
objects: []client.Object{
2494+
&corev1.Secret{
2495+
ObjectMeta: metav1.ObjectMeta{
2496+
Name: "cloud-credentials",
2497+
Namespace: "test-ns",
2498+
},
2499+
Data: map[string][]byte{
2500+
"credentials": []byte("fake-creds"),
2501+
},
2502+
},
2503+
},
2504+
wantErr: false,
2505+
},
24502506
}
24512507
for _, tt := range tests {
24522508
tt.objects = append(tt.objects, tt.dpa)
@@ -2483,3 +2539,220 @@ func TestDPAReconciler_ValidateDataProtectionCR(t *testing.T) {
24832539
})
24842540
}
24852541
}
2542+
2543+
func TestDPAReconciler_ValidateDataProtectionCR_PodAnnotationsDeprecationWarning(t *testing.T) {
2544+
// Create a test logger that captures log messages
2545+
var logOutput []string
2546+
testLogger := logr.New(&testLogSink{logs: &logOutput})
2547+
2548+
// DPA with deprecated PodAnnotations field
2549+
dpa := &oadpv1alpha1.DataProtectionApplication{
2550+
ObjectMeta: metav1.ObjectMeta{
2551+
Name: "test-DPA-CR",
2552+
Namespace: "test-ns",
2553+
},
2554+
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
2555+
PodAnnotations: map[string]string{
2556+
"deprecated.annotation": "value",
2557+
},
2558+
BackupLocations: []oadpv1alpha1.BackupLocation{
2559+
{
2560+
Velero: &velerov1.BackupStorageLocationSpec{
2561+
Provider: "aws",
2562+
StorageType: velerov1.StorageType{
2563+
ObjectStorage: &velerov1.ObjectStorageLocation{
2564+
Bucket: "bucket",
2565+
},
2566+
},
2567+
Config: map[string]string{
2568+
"region": "us-east-1",
2569+
},
2570+
Credential: &corev1.SecretKeySelector{
2571+
LocalObjectReference: corev1.LocalObjectReference{
2572+
Name: "cloud-credentials",
2573+
},
2574+
Key: "credentials",
2575+
},
2576+
Default: true,
2577+
},
2578+
},
2579+
},
2580+
Configuration: &oadpv1alpha1.ApplicationConfig{
2581+
Velero: &oadpv1alpha1.VeleroConfig{
2582+
DefaultPlugins: []oadpv1alpha1.DefaultPlugin{
2583+
oadpv1alpha1.DefaultPluginAWS,
2584+
},
2585+
},
2586+
},
2587+
BackupImages: ptr.To(false),
2588+
},
2589+
}
2590+
2591+
// Add required secret for AWS plugin validation
2592+
cloudCredentialsSecret := &corev1.Secret{
2593+
ObjectMeta: metav1.ObjectMeta{
2594+
Name: "cloud-credentials",
2595+
Namespace: "test-ns",
2596+
},
2597+
Data: map[string][]byte{
2598+
"credentials": []byte("fake-creds"),
2599+
},
2600+
}
2601+
2602+
objects := []client.Object{dpa, cloudCredentialsSecret}
2603+
fakeClient, err := getFakeClientFromObjects(objects...)
2604+
if err != nil {
2605+
t.Errorf("error in creating fake client: %v", err)
2606+
return
2607+
}
2608+
2609+
r := &DataProtectionApplicationReconciler{
2610+
Client: fakeClient,
2611+
ClusterWideClient: fakeClient,
2612+
Scheme: fakeClient.Scheme(),
2613+
Log: testLogger,
2614+
Context: newContextForTest(),
2615+
NamespacedName: types.NamespacedName{
2616+
Namespace: dpa.Namespace,
2617+
Name: dpa.Name,
2618+
},
2619+
dpa: dpa,
2620+
EventRecorder: record.NewFakeRecorder(10),
2621+
}
2622+
2623+
// Run validation
2624+
valid, err := r.ValidateDataProtectionCR(testLogger)
2625+
if err != nil {
2626+
t.Errorf("ValidateDataProtectionCR() unexpected error = %v", err)
2627+
return
2628+
}
2629+
if !valid {
2630+
t.Errorf("ValidateDataProtectionCR() = %v, want true", valid)
2631+
return
2632+
}
2633+
2634+
// Check that deprecation warning was logged
2635+
found := false
2636+
for _, log := range logOutput {
2637+
if log == "(Deprecation Warning) The 'podAnnotations' field is deprecated. Please migrate to 'configuration.velero.podConfig.annotations' for Velero pods and 'configuration.nodeAgent.podConfig.annotations' for NodeAgent pods." {
2638+
found = true
2639+
break
2640+
}
2641+
}
2642+
if !found {
2643+
t.Errorf("Expected deprecation warning was not logged. Logs: %v", logOutput)
2644+
}
2645+
}
2646+
2647+
// Test DPA without PodAnnotations should not log warning
2648+
func TestDPAReconciler_ValidateDataProtectionCR_NoPodAnnotationsNoWarning(t *testing.T) {
2649+
// Create a test logger that captures log messages
2650+
var logOutput []string
2651+
testLogger := logr.New(&testLogSink{logs: &logOutput})
2652+
2653+
// DPA without deprecated PodAnnotations field
2654+
dpa := &oadpv1alpha1.DataProtectionApplication{
2655+
ObjectMeta: metav1.ObjectMeta{
2656+
Name: "test-DPA-CR",
2657+
Namespace: "test-ns",
2658+
},
2659+
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
2660+
BackupLocations: []oadpv1alpha1.BackupLocation{
2661+
{
2662+
Velero: &velerov1.BackupStorageLocationSpec{
2663+
Provider: "aws",
2664+
StorageType: velerov1.StorageType{
2665+
ObjectStorage: &velerov1.ObjectStorageLocation{
2666+
Bucket: "bucket",
2667+
},
2668+
},
2669+
Config: map[string]string{
2670+
"region": "us-east-1",
2671+
},
2672+
Credential: &corev1.SecretKeySelector{
2673+
LocalObjectReference: corev1.LocalObjectReference{
2674+
Name: "cloud-credentials",
2675+
},
2676+
Key: "credentials",
2677+
},
2678+
Default: true,
2679+
},
2680+
},
2681+
},
2682+
Configuration: &oadpv1alpha1.ApplicationConfig{
2683+
Velero: &oadpv1alpha1.VeleroConfig{
2684+
DefaultPlugins: []oadpv1alpha1.DefaultPlugin{
2685+
oadpv1alpha1.DefaultPluginAWS,
2686+
},
2687+
},
2688+
},
2689+
BackupImages: ptr.To(false),
2690+
},
2691+
}
2692+
2693+
// Add required secret for AWS plugin validation
2694+
cloudCredentialsSecret := &corev1.Secret{
2695+
ObjectMeta: metav1.ObjectMeta{
2696+
Name: "cloud-credentials",
2697+
Namespace: "test-ns",
2698+
},
2699+
Data: map[string][]byte{
2700+
"credentials": []byte("fake-creds"),
2701+
},
2702+
}
2703+
2704+
objects := []client.Object{dpa, cloudCredentialsSecret}
2705+
fakeClient, err := getFakeClientFromObjects(objects...)
2706+
if err != nil {
2707+
t.Errorf("error in creating fake client: %v", err)
2708+
return
2709+
}
2710+
2711+
r := &DataProtectionApplicationReconciler{
2712+
Client: fakeClient,
2713+
ClusterWideClient: fakeClient,
2714+
Scheme: fakeClient.Scheme(),
2715+
Log: testLogger,
2716+
Context: newContextForTest(),
2717+
NamespacedName: types.NamespacedName{
2718+
Namespace: dpa.Namespace,
2719+
Name: dpa.Name,
2720+
},
2721+
dpa: dpa,
2722+
EventRecorder: record.NewFakeRecorder(10),
2723+
}
2724+
2725+
// Run validation
2726+
valid, err := r.ValidateDataProtectionCR(testLogger)
2727+
if err != nil {
2728+
t.Errorf("ValidateDataProtectionCR() unexpected error = %v", err)
2729+
return
2730+
}
2731+
if !valid {
2732+
t.Errorf("ValidateDataProtectionCR() = %v, want true", valid)
2733+
return
2734+
}
2735+
2736+
// Check that deprecation warning was NOT logged
2737+
for _, log := range logOutput {
2738+
if log == "(Deprecation Warning) The 'podAnnotations' field is deprecated. Please migrate to 'configuration.velero.podConfig.annotations' for Velero pods and 'configuration.nodeAgent.podConfig.annotations' for NodeAgent pods." {
2739+
t.Errorf("Deprecation warning should not be logged when PodAnnotations is not used. Logs: %v", logOutput)
2740+
}
2741+
}
2742+
}
2743+
2744+
// testLogSink implements logr.LogSink for testing
2745+
type testLogSink struct {
2746+
logs *[]string
2747+
}
2748+
2749+
func (t *testLogSink) Init(info logr.RuntimeInfo) {}
2750+
func (t *testLogSink) Enabled(level int) bool { return true }
2751+
func (t *testLogSink) Info(level int, msg string, keysAndValues ...interface{}) {
2752+
*t.logs = append(*t.logs, msg)
2753+
}
2754+
func (t *testLogSink) Error(err error, msg string, keysAndValues ...interface{}) {
2755+
*t.logs = append(*t.logs, msg)
2756+
}
2757+
func (t *testLogSink) WithValues(keysAndValues ...interface{}) logr.LogSink { return t }
2758+
func (t *testLogSink) WithName(name string) logr.LogSink { return t }

0 commit comments

Comments
 (0)