Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/common/constant/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const JSONTagString = "json"
// CommaString defines a constant for the comma string
const CommaString = ","

// WildcardString defines the wildcard character used in Velero resource filters
const WildcardString = "*"

// MaximumNacObjectNameLength represents Generated Non Admin Object Name and
// must be below 63 characters, because it's used within object Label Value
const MaximumNacObjectNameLength = validation.DNS1123LabelMaxLength
Expand Down
5 changes: 5 additions & 0 deletions internal/common/function/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,11 @@ func TestValidateBackupSpecEnforcedFields(t *testing.T) {
ParallelFilesUpload: 32, //nolint:revive // just test
},
},
{
name: "VolumeGroupSnapshotLabelKey",
enforcedValue: "enforced-label-key",
overrideValue: "user-label-key",
},
Comment on lines +381 to +385
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify VolumeGroupSnapshotLabelKey field exists in Velero BackupSpec

# Check go.mod for Velero version
echo "=== Velero version in go.mod ==="
rg -n 'github.com/vmware-tanzu/velero' go.mod

# Search for VolumeGroupSnapshotLabelKey usage in the codebase
echo -e "\n=== VolumeGroupSnapshotLabelKey references ==="
rg -n 'VolumeGroupSnapshotLabelKey'

# Verify the field exists in imported Velero types
echo -e "\n=== BackupSpec struct fields (if vendored) ==="
fd -t f 'backup_types.go' --exec grep -A 100 'type BackupSpec struct' {} \; 2>/dev/null | head -150

Repository: migtools/oadp-non-admin

Length of output: 736


🏁 Script executed:

#!/bin/bash
# Check the BackupSpec struct definition in the project
echo "=== Looking for BackupSpec struct in project imports ==="
rg -n 'type BackupSpec struct' --type go

# Check the imports in function_test.go to see which Velero version is being used
echo -e "\n=== Imports in function_test.go ==="
head -50 internal/common/function/function_test.go | grep -A 20 'import'

# Check the completeness check logic at lines 460-475
echo -e "\n=== Completeness check logic ==="
sed -n '455,480p' internal/common/function/function_test.go

Repository: migtools/oadp-non-admin

Length of output: 1531


🏁 Script executed:

#!/bin/bash
# Check when VolumeGroupSnapshotLabelKey was added to Velero
echo "=== Searching Velero upstream for VolumeGroupSnapshotLabelKey ==="

# Try to find any Velero types file that might be in vendor or cached
fd -t f -name '*velero*' -type f | grep -E '(types|backup)' | head -20

# Check if there's a vendor directory with Velero types
echo -e "\n=== Checking for vendored Velero types ==="
find . -path '*/velero/pkg/apis/velero/v1/*' -name '*.go' 2>/dev/null | head -10

# Look for any BackupSpec references that show actual field names
echo -e "\n=== Looking for BackupSpec field references in YAML/config ==="
rg -A 50 'type.*BackupSpec' --type go 2>/dev/null | head -100

Repository: migtools/oadp-non-admin

Length of output: 4242


🌐 Web query:

Velero VolumeGroupSnapshotLabelKey release version when added

💡 Result:

The VolumeGroupSnapshotLabelKey feature, which allows customizing the label key used by Velero to group PVCs for VolumeGroupSnapshots (default: velero.io/volume-group), was added in Velero v1.17.0. It is configurable via the Backup spec field volumeGroupSnapshotLabelKey, the server flag --volume-group-snapshot-label-key, or defaults to velero.io/volume-group.

Citations:


CRITICAL: Test case references a non-existent BackupSpec field in current Velero version.

The VolumeGroupSnapshotLabelKey field was added in Velero v1.17.0, but this project uses v1.14.0. The test case at lines 381-385 will fail when the completeness check (lines 460-475) attempts to validate the field against the actual velerov1.BackupSpec struct via reflection. The field does not exist in v1.14.0's BackupSpec.

Either upgrade Velero to v1.17.0 or later, or remove this test case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/common/function/function_test.go` around lines 381 - 385, The test
includes a case named "VolumeGroupSnapshotLabelKey" that references
BackupSpec.VolumeGroupSnapshotLabelKey which does not exist in the vendored
Velero v1.14.0; remove that test case (or replace it with a field present in
v1.14.0) so the completeness check in function_test.go (the reflection-based
validation around lines 460-475) no longer expects the nonexistent field;
alternatively, if you intend to keep the test, upgrade the Velero dependency to
v1.17.0+ so BackupSpec contains VolumeGroupSnapshotLabelKey and the reflection
check will pass.

}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
18 changes: 13 additions & 5 deletions internal/controller/nonadminbackup_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"errors"
"reflect"
"slices"

"github.com/go-logr/logr"
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
Expand Down Expand Up @@ -705,11 +706,18 @@ func (r *NonAdminBackupReconciler) createVeleroBackupAndSyncWithNonAdminBackup(c

if haveNewResourceFilterParameters {
// Use the new-style exclusion list
backupSpec.ExcludedNamespaceScopedResources = append(backupSpec.ExcludedNamespaceScopedResources,
alwaysExcludedNamespacedResources...)
backupSpec.ExcludedClusterScopedResources = append(backupSpec.ExcludedClusterScopedResources,
alwaysExcludedClusterResources...)
} else {
// Skip appending when wildcard '*' is already present, as it already
// excludes everything and mixing '*' with specific items causes Velero
// FailedValidation.
if !slices.Contains(backupSpec.ExcludedNamespaceScopedResources, constant.WildcardString) {
backupSpec.ExcludedNamespaceScopedResources = append(backupSpec.ExcludedNamespaceScopedResources,
alwaysExcludedNamespacedResources...)
}
if !slices.Contains(backupSpec.ExcludedClusterScopedResources, constant.WildcardString) {
backupSpec.ExcludedClusterScopedResources = append(backupSpec.ExcludedClusterScopedResources,
alwaysExcludedClusterResources...)
}
Comment on lines +709 to +719
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the old-style ExcludedResources field also supports wildcards in Velero
# and find all similar append patterns that might need the same fix

# Find other places where resources are appended to exclusion lists without wildcard check
rg -n -A3 -B3 'ExcludedResources\s*=\s*append' --type=go

# Check Velero documentation/validation for wildcard handling in ExcludedResources
rg -n 'ExcludedResources.*\*' --type=go

Repository: migtools/oadp-non-admin

Length of output: 1363


🏁 Script executed:

# Get context around the fallback path in backup controller
sed -n '700,730p' internal/controller/nonadminbackup_controller.go

Repository: migtools/oadp-non-admin

Length of output: 1556


🏁 Script executed:

# Get context around the restore controller append
sed -n '340,360p' internal/controller/nonadminrestore_controller.go

Repository: migtools/oadp-non-admin

Length of output: 873


🏁 Script executed:

# Check if there are any tests or comments that mention ExcludedResources wildcard behavior
rg -i 'excludedresources.*wildcard|wildcard.*excludedresources' --type=go -C 3

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Search for ExcludedResources field definitions or usage patterns to understand its purpose
rg -n 'ExcludedResources' --type=go | head -20

Repository: migtools/oadp-non-admin

Length of output: 1038


🏁 Script executed:

# Find the definition of alwaysExcludedNamespacedResources and alwaysExcludedClusterResources
rg -n 'alwaysExcluded(Namespaced|Cluster)Resources\s*[=:]' --type=go -A 10

Repository: migtools/oadp-non-admin

Length of output: 1312


🏁 Script executed:

# Check if there are any constants or variables that might contain wildcards
rg -n 'alwaysExcluded' --type=go

Repository: migtools/oadp-non-admin

Length of output: 637


🏁 Script executed:

# Look for Velero API types to understand ExcludedResources field
rg -n 'type.*Spec.*struct' --type=go | grep -i backup | head -5

Repository: migtools/oadp-non-admin

Length of output: 359


🏁 Script executed:

# Check if there are any Velero imports or comments that explain ExcludedResources behavior
rg -n 'velerov1\.(Backup|Restore)' --type=go -B 2 -A 2 | head -40

Repository: migtools/oadp-non-admin

Length of output: 3473


🏁 Script executed:

# Look for test cases that show ExcludedResources behavior with wildcards
rg -n 'ExcludedResources.*\*|"\*".*ExcludedResources' --type=go -B 2 -A 2

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Check if the code has any other wildcard validation patterns
rg -n 'slices.Contains.*\*' --type=go -B 2 -A 2

Repository: migtools/oadp-non-admin

Length of output: 1078


🏁 Script executed:

# Verify the actual risk: check if user can set ExcludedResources to "*"
rg -n 'ExcludedResources\s*:' --type=go | grep -v '//'

Repository: migtools/oadp-non-admin

Length of output: 254


🏁 Script executed:

# Check the actual BackupSpec and RestoreSpec type definitions to confirm ExcludedResources is user-provided
fd -t f -e go -x grep -l 'type.*BackupSpec\|type.*RestoreSpec' {} \; 2>/dev/null | head -10

Repository: migtools/oadp-non-admin

Length of output: 197


🏁 Script executed:

# Verify if there are any tests that show user setting ExcludedResources with "*"
rg -n 'ExcludedResources.*=.*\[\]string' --type=go -A 3 | head -30

Repository: migtools/oadp-non-admin

Length of output: 889


🏁 Script executed:

# Check NonAdminBackupSpec to see if it embeds BackupSpec
sed -n '27,100p' api/v1alpha1/nonadminbackup_types.go

Repository: migtools/oadp-non-admin

Length of output: 2728


🏁 Script executed:

# Check if ExcludedResources is user-configurable by looking at how it's populated
rg -n 'backupSpec.ExcludedResources\s*=' --type=go -B 3 -A 1

Repository: migtools/oadp-non-admin

Length of output: 723


🏁 Script executed:

# Look for where ExcludedResources might be set by users (in API or spec parsing)
rg -n 'ExcludedResources' --type=go | grep -v 'append\|test\|//'

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Check if there's any existing pattern for checking wildcards in ExcludedResources
rg -n 'Contains.*ExcludedResources' --type=go -B 2 -A 2

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Check the actual field definitions in Velero's BackupSpec to understand ExcludedResources
# Since we can't access Velero directly, look for any documentation or comments in this repo
rg -n 'ExcludedResources' --type=go -B 5 | grep -E 'type|struct|comment|//' | head -20

Repository: migtools/oadp-non-admin

Length of output: 166


🏁 Script executed:

# Look at how the backup is created with ExcludedResources to understand the risk
sed -n '697,730p' internal/controller/nonadminbackup_controller.go

Repository: migtools/oadp-non-admin

Length of output: 1645


🏁 Script executed:

# Check if there's any validation or comment about the wildcard behavior being specific to scoped fields
rg -n 'wildcard|Wildcard|\*' --type=go -B 2 -A 2 | grep -A 2 -B 2 'scoped\|Scoped' | head -30

Repository: migtools/oadp-non-admin

Length of output: 2519


🏁 Script executed:

# Verify if the test actually shows the wildcard behavior being a Velero validation issue
sed -n '1549,1575p' internal/controller/nonadminbackup_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 1092


🏁 Script executed:

# Check what validation errors Velero throws when mixing wildcards with specific items
# Look for any error handling or validation in the test files
rg -n 'FailedValidation' --type=go -B 3 -A 3

Repository: migtools/oadp-non-admin

Length of output: 868


🏁 Script executed:

# Understand the user flow: can a user actually set ExcludedResources to "*" in the old-style path?
# Check if there are any examples in tests showing user setting this
rg -n 'BackupSpec.*{' --type=go -A 20 | grep -A 15 'ExcludedResources'

Repository: migtools/oadp-non-admin

Length of output: 156


🏁 Script executed:

# Check if there's any documentation about when ExcludedResources vs scoped resources are used
sed -n '697,710p' internal/controller/nonadminbackup_controller.go

Repository: migtools/oadp-non-admin

Length of output: 787


🏁 Script executed:

# Search for any comments or issues related to the old-style path wildcard handling
rg -n 'old-style|fallback' --type=go -B 3 -A 3 | grep -i 'wildcard\|exclude' -B 3 -A 3

Repository: migtools/oadp-non-admin

Length of output: 724


🏁 Script executed:

# Check if there are tests for the fallback path with ExcludedResources containing wildcards
rg -n 'Should not append default excluded resources' --type=go -A 30 | grep -B 30 'ExcludedResources'

Repository: migtools/oadp-non-admin

Length of output: 49


Add wildcard guard to old-style fallback path and restore controller.

The new-style scoped fields correctly prevent appending specific resources when "*" is present. However, the old-style fallback path (lines 722-725) unconditionally appends resources without checking for "*", which would violate Velero's validation if a user sets ExcludedResources: ["*"]. The same gap exists in nonadminrestore_controller.go (lines 351-352).

Add wildcard checks before appending in both locations:

  1. Fallback path (lines 722-725): Wrap both appends with if !slices.Contains(backupSpec.ExcludedResources, "*")
  2. Restore controller (lines 351-352): Wrap the append with if !slices.Contains(restoreSpec.ExcludedResources, "*")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/nonadminbackup_controller.go` around lines 709 - 719, The
fallback path unconditionally appends to old-style ExcludedResources which
breaks validation when a user has ExcludedResources: ["*"]; update the logic in
the non-admin backup and restore controllers to check for the wildcard before
appending: in the backup flow, guard the two append calls with if
!slices.Contains(backupSpec.ExcludedResources, "*") (targeting the block that
currently appends
alwaysExcludedNamespacedResources/alwaysExcludedClusterResources when scoped
fields are absent), and in the restore flow guard the append with if
!slices.Contains(restoreSpec.ExcludedResources, "*") so nothing is appended when
the wildcard already excludes everything.

} else if !slices.Contains(backupSpec.ExcludedResources, constant.WildcardString) {
// Fallback to the old-style exclusion list
backupSpec.ExcludedResources = append(backupSpec.ExcludedResources,
alwaysExcludedNamespacedResources...)
Expand Down
38 changes: 38 additions & 0 deletions internal/controller/nonadminbackup_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,44 @@ var _ = ginkgo.Describe("Test full reconcile loop of NonAdminBackup Controller",
},
addSyncLabel: true,
}),
ginkgo.Entry("Should not append default excluded resources when wildcard is used in excludedClusterScopedResources", nonAdminBackupFullReconcileScenario{
spec: nacv1alpha1.NonAdminBackupSpec{
BackupSpec: &velerov1.BackupSpec{
ExcludedClusterScopedResources: []string{"*"},
IncludedNamespaceScopedResources: []string{"pvc"},
},
},
status: nacv1alpha1.NonAdminBackupStatus{
Phase: nacv1alpha1.NonAdminPhaseCreated,
VeleroBackup: &nacv1alpha1.VeleroBackup{
Namespace: oadpNamespace,
Status: nil,
Spec: &velerov1.BackupSpec{
ExcludedClusterScopedResources: []string{"*"},
IncludedNamespaceScopedResources: []string{"pvc"},
ExcludedNamespaceScopedResources: []string{
nacv1alpha1.NonAdminBackups,
nacv1alpha1.NonAdminRestores,
nacv1alpha1.NonAdminBackupStorageLocations,
},
},
},
Conditions: []metav1.Condition{
{
Type: "Accepted",
Status: metav1.ConditionTrue,
Reason: "BackupAccepted",
Message: "backup accepted",
},
{
Type: "Queued",
Status: metav1.ConditionTrue,
Reason: "BackupScheduled",
Message: "Created Velero Backup object",
},
},
},
}),
)

ginkgo.DescribeTable("Reconcile triggered by NonAdminBackup sync event",
Expand Down
98 changes: 50 additions & 48 deletions internal/controller/nonadminbackupstoragelocation_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,37 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

RESOURCE CONFLICT RESOLUTION ENHANCEMENTS:
This file has been enhanced to resolve resource conflict issues that occurred when
multiple controllers or processes attempted to update the same NonAdminBackupStorageLocationRequest
objects simultaneously. The following changes were made:

1. RETRY LOGIC FRAMEWORK (updateStatusWithRetry function):
- Uses standard Kubernetes client-go retry.RetryOnConflict with DefaultRetry settings
- Handles "object has been modified" errors gracefully
- Fetches fresh object copies to avoid stale ResourceVersion conflicts
- Leverages proven Kubernetes retry patterns (5 attempts, 10ms+jitter)

2. NIL SAFETY CHECKS (ensureNonAdminRequest function):
- Prevents panic when SourceNonAdminBSL is nil during initialization
- Converts terminal errors to requeue conditions for uninitialized status
- Allows proper status initialization timing in high-concurrency environments

3. OPTIMIZED STATUS UPDATES (createNonAdminRequest function):
- Uses fast-path direct updates for new objects
- Falls back to retry logic only when conflicts are detected
- Preserves computed status values while ensuring conflict resilience

4. TEST ENVIRONMENT ADAPTATIONS:
- Increased timeouts to accommodate retry logic execution time
- Reduced polling frequency to handle Kubernetes client rate limiting
- Added delays to prevent overwhelming API server during test runs

These enhancements ensure that OADP non-admin backup operations complete successfully
even under high concurrency or when multiple reconciliation events occur simultaneously.
*/

// RESOURCE CONFLICT RESOLUTION ENHANCEMENTS:
// This file has been enhanced to resolve resource conflict issues that occurred when
// multiple controllers or processes attempted to update the same NonAdminBackupStorageLocationRequest
// objects simultaneously. The following changes were made:
//
// 1. RETRY LOGIC FRAMEWORK (updateStatusWithRetry function):
// - Uses standard Kubernetes client-go retry.RetryOnConflict with DefaultRetry settings
// - Handles "object has been modified" errors gracefully
// - Fetches fresh object copies to avoid stale ResourceVersion conflicts
// - Leverages proven Kubernetes retry patterns (5 attempts, 10ms+jitter)
//
// 2. NIL SAFETY CHECKS (ensureNonAdminRequest function):
// - Prevents panic when SourceNonAdminBSL is nil during initialization
// - Converts terminal errors to requeue conditions for uninitialized status
// - Allows proper status initialization timing in high-concurrency environments
//
// 3. OPTIMIZED STATUS UPDATES (createNonAdminRequest function):
// - Uses fast-path direct updates for new objects
// - Falls back to retry logic only when conflicts are detected
// - Preserves computed status values while ensuring conflict resilience
//
// 4. TEST ENVIRONMENT ADAPTATIONS:
// - Increased timeouts to accommodate retry logic execution time
// - Reduced polling frequency to handle Kubernetes client rate limiting
// - Added delays to prevent overwhelming API server during test runs
//
// These enhancements ensure that OADP non-admin backup operations complete successfully
// even under high concurrency or when multiple reconciliation events occur simultaneously.

package controller

import (
Expand Down Expand Up @@ -125,7 +125,10 @@ func (r *NonAdminBackupStorageLocationReconciler) updateStatusWithRetry(ctx cont
// Get the latest version of the object from the API server to ensure we have
// the most recent ResourceVersion and avoid stale object conflicts
key := client.ObjectKeyFromObject(obj)
fresh := obj.DeepCopyObject().(client.Object)
fresh, ok := obj.DeepCopyObject().(client.Object)
if !ok {
return errors.New("failed to convert deep copy to client.Object")
}
if err := r.Get(ctx, key, fresh); err != nil {
return err // RetryOnConflict will handle conflict vs non-conflict errors
}
Expand Down Expand Up @@ -643,7 +646,10 @@ func (r *NonAdminBackupStorageLocationReconciler) createNonAdminRequest(ctx cont
// - Event-driven reconciliation causing concurrent status updates
logger.V(1).Info("NonAdminBackupStorageLocationRequest already exists")
if updateErr := r.updateStatusWithRetry(ctx, logger, nabslRequest, func(obj client.Object) bool {
req := obj.(*nacv1alpha1.NonAdminBackupStorageLocationRequest)
req, ok := obj.(*nacv1alpha1.NonAdminBackupStorageLocationRequest)
if !ok {
return false
}
return updatePhaseIfNeeded(&req.Status.Phase, req.Spec.ApprovalDecision)
}); updateErr != nil {
logger.Error(updateErr, failedUpdateStatusError)
Expand Down Expand Up @@ -704,26 +710,22 @@ func (r *NonAdminBackupStorageLocationReconciler) createNonAdminRequest(ctx cont
// - Correctness: Proper status initialization even under load
if updated := updateNonAdminRequestStatus(&nonAdminBslRequest.Status, nabsl, approvalDecision); updated {
if updateErr := r.Status().Update(ctx, &nonAdminBslRequest); updateErr != nil {
if apierrors.IsConflict(updateErr) {
// CONFLICT DETECTED: Another process modified the request between create and status update
// This can happen when:
// - Admin approves/rejects the request immediately after creation
// - Multiple reconcile loops are triggered by related events
// - High concurrency in the test environment
logger.V(1).Info("Conflict on initial status update, retrying with fresh object...")
if retryErr := r.updateStatusWithRetry(ctx, logger, &nonAdminBslRequest, func(obj client.Object) bool {
req := obj.(*nacv1alpha1.NonAdminBackupStorageLocationRequest)
return updateNonAdminRequestStatus(&req.Status, nabsl, approvalDecision)
}); retryErr != nil {
logger.Error(retryErr, failedUpdateStatusError)
return false, retryErr
}
} else {
// NON-CONFLICT ERROR: Validation, permission, or other API server issue
// Don't retry these as they're likely to persist
if !apierrors.IsConflict(updateErr) {
logger.Error(updateErr, failedUpdateStatusError)
return false, updateErr
}
// CONFLICT DETECTED: Another process modified the request between create and status update
logger.V(1).Info("Conflict on initial status update, retrying with fresh object...")
if retryErr := r.updateStatusWithRetry(ctx, logger, &nonAdminBslRequest, func(obj client.Object) bool {
req, ok := obj.(*nacv1alpha1.NonAdminBackupStorageLocationRequest)
if !ok {
return false
}
return updateNonAdminRequestStatus(&req.Status, nabsl, approvalDecision)
}); retryErr != nil {
logger.Error(retryErr, failedUpdateStatusError)
return false, retryErr
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions internal/controller/nonadminrestore_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"reflect"
"slices"

"github.com/go-logr/logr"
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
Expand Down Expand Up @@ -348,8 +349,10 @@ func (r *NonAdminRestoreReconciler) createVeleroRestore(ctx context.Context, log
}
}

restoreSpec.ExcludedResources = append(restoreSpec.ExcludedResources,
"volumesnapshotclasses")
if !slices.Contains(restoreSpec.ExcludedResources, constant.WildcardString) {
restoreSpec.ExcludedResources = append(restoreSpec.ExcludedResources,
"volumesnapshotclasses")
}
Comment on lines +352 to +355
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if there are tests covering the wildcard exclusion scenario for restores

# Search for test cases involving ExcludedResources with wildcard in restore controller tests
rg -n -C5 'ExcludedResources.*\*|"\*"' internal/controller/nonadminrestore_controller_test.go

# Also check if there's any wildcard-related test naming
rg -n 'wildcard|Wildcard' internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Check the implementation at lines 352-355
cat -n internal/controller/nonadminrestore_controller.go | sed -n '350,360p'

Repository: migtools/oadp-non-admin

Length of output: 479


🏁 Script executed:

# Check the test file structure and the mentioned lines 369-370
wc -l internal/controller/nonadminrestore_controller_test.go
head -n 1 internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 128


🏁 Script executed:

# Look at the test file around lines 369-370 to see what's being tested
sed -n '365,380p' internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 779


🏁 Script executed:

# Search for any test setup with wildcard patterns in ExcludedResources across the entire test file
rg -n 'ExcludedResources' internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 143


🏁 Script executed:

# Look for scenario setup that might include wildcard configurations
rg -n 'scenario\.|WildcardString' internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 1682


🏁 Script executed:

# Check what WildcardString constant is defined as
rg -n 'WildcardString.*=' internal/pkg/constant/ | head -5

Repository: migtools/oadp-non-admin

Length of output: 129


🏁 Script executed:

# Look for any scenario definitions in the test file to understand test structure
sed -n '1,150p' internal/controller/nonadminrestore_controller_test.go | grep -A2 'type.*scenario\|var.*scenario' | head -20

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Find where WildcardString is defined
rg -n 'WildcardString' --type go | head -10

Repository: migtools/oadp-non-admin

Length of output: 821


🏁 Script executed:

# Look at the test scenarios structure - search for test case definitions
sed -n '80,200p' internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 5489


🏁 Script executed:

# Search for test scenario definitions in the test file
rg -n 'nonAdminRestoreFullReconcileScenario\{' internal/controller/nonadminrestore_controller_test.go -A 15 | head -100

Repository: migtools/oadp-non-admin

Length of output: 2266


🏁 Script executed:

# Look for all test entry definitions with Entry() to see if any test the wildcard scenario
rg -n 'ginkgo.Entry' internal/controller/nonadminrestore_controller_test.go -B 2 -A 8 | head -150

Repository: migtools/oadp-non-admin

Length of output: 1917


🏁 Script executed:

# Look at the test scenario structures to see if any include ExcludedResources with wildcard
sed -n '451,610p' internal/controller/nonadminrestore_controller_test.go | grep -C 10 'ExcludedResources'

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Search for all test scenario definitions to find if wildcard is tested
rg -n 'ginkgo.Entry.*"Should' internal/controller/nonadminrestore_controller_test.go

Repository: migtools/oadp-non-admin

Length of output: 638


🏁 Script executed:

# Get more details on what test scenarios exist and their structure
sed -n '451,625p' internal/controller/nonadminrestore_controller_test.go | head -200

Repository: migtools/oadp-non-admin

Length of output: 5120


🏁 Script executed:

# Check if there's any test that explicitly uses wildcard in ExcludedResources for restore tests
rg -n '\*' internal/controller/nonadminrestore_controller_test.go | grep -i 'exclude\|resource' | head -10

Repository: migtools/oadp-non-admin

Length of output: 49


🏁 Script executed:

# Look at the backup controller to see if it has similar tests and if wildcard scenarios are tested there
rg -n 'wildcard\|WildcardString' internal/controller/nonadminbackup_controller_test.go -B 2 -A 2

Repository: migtools/oadp-non-admin

Length of output: 49


Confirm test coverage gap: add test case for wildcard exclusion scenario.

The implementation correctly prevents appending volumesnapshotclasses when "*" is already in ExcludedResources, avoiding Velero validation failures. However, test coverage exists only for the normal case where volumesnapshotclasses is appended (line 369). A test case is missing to verify that when ExcludedResources = ["*"], it remains unchanged after processing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/nonadminrestore_controller.go` around lines 352 - 355,
Add a unit test that exercises the code path which checks
slices.Contains(restoreSpec.ExcludedResources, constant.WildcardString) so we
verify the wildcard case is preserved: create a Restore (or restoreSpec) with
ExcludedResources set to []string{constant.WildcardString}, invoke the
controller logic that runs the exclusion-normalization (the code in
nonadminrestore_controller.go — the Reconcile / processing function that
contains the slices.Contains check), and assert that ExcludedResources still
equals []string{constant.WildcardString} (i.e., "volumesnapshotclasses" is not
appended). Ensure the test uses the same symbols restoreSpec.ExcludedResources
and constant.WildcardString for clarity.


veleroRestore = &velerov1.Restore{
ObjectMeta: metav1.ObjectMeta{
Expand Down