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
111 changes: 111 additions & 0 deletions tests/e2e/framework/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,3 +613,114 @@ func getTestMetricsCMD(namespace string) string {
func GetPoolNodeRoleSelector() map[string]string {
return utils.GetNodeRoleSelector(TestPoolName)
}

// GetDefaultStorageClassProvisioner retrieves the provisioner from the default storage class
func (f *Framework) GetDefaultStorageClassProvisioner() (string, error) {
var scList unstructured.UnstructuredList
scList.SetGroupVersionKind(schema.GroupVersionKind{
Group: "storage.k8s.io",
Version: "v1",
Kind: "StorageClassList",
})

if err := f.Client.List(context.TODO(), &scList); err != nil {
return "", fmt.Errorf("failed to list storage classes: %w", err)
}

for _, sc := range scList.Items {
annotations := sc.GetAnnotations()
if annotations != nil {
if isDefault, ok := annotations["storageclass.kubernetes.io/is-default-class"]; ok && isDefault == "true" {
provisioner, found, err := unstructured.NestedString(sc.Object, "provisioner")
if err != nil {
return "", fmt.Errorf("failed to get provisioner from storage class: %w", err)
}
if !found {
return "", fmt.Errorf("provisioner field not found in default storage class")
}
return provisioner, nil
}
}
}

return "", fmt.Errorf("no default storage class found in the cluster")
}

// CreateCustomStorageClass creates a custom StorageClass object
func (f *Framework) CreateCustomStorageClass(name, provisioner string) (*unstructured.Unstructured, error) {
sc := &unstructured.Unstructured{}
sc.SetGroupVersionKind(schema.GroupVersionKind{
Group: "storage.k8s.io",
Version: "v1",
Kind: "StorageClass",
})
sc.SetName(name)
if err := unstructured.SetNestedField(sc.Object, provisioner, "provisioner"); err != nil {
return nil, fmt.Errorf("failed to set provisioner field: %w", err)
}
return sc, nil
}

// AssertScanPVCHasStorageConfig verifies that the PVC for a scan has the expected storage configuration
func (f *Framework) AssertScanPVCHasStorageConfig(scanName, namespace, expectedStorageClassName string, expectedAccessMode corev1.PersistentVolumeAccessMode) error {
// List all PVCs in the namespace
pvcList := &corev1.PersistentVolumeClaimList{}
listOpts := &client.ListOptions{
Namespace: namespace,
}
if err := f.Client.List(context.TODO(), pvcList, listOpts); err != nil {
return fmt.Errorf("failed to list PVCs: %w", err)
}

// Find the PVC for the scan
var scanPVC *corev1.PersistentVolumeClaim
for i := range pvcList.Items {
pvc := &pvcList.Items[i]
// Check if this PVC belongs to our scan
if pvc.Labels != nil {
if scanLabel, ok := pvc.Labels[compv1alpha1.ComplianceScanLabel]; ok && scanLabel == scanName {
scanPVC = pvc
break
}
}
// Alternative: check if the PVC name contains our scan name
Copy link
Collaborator

Choose a reason for hiding this comment

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

This might not be necessary since the label should always map to the scan name (a containment check on strings will be more fragile than checking an exact match).

https://github.com/ComplianceAsCode/compliance-operator/blob/master/pkg/controller/compliancescan/rawresults.go#L80

Copy link
Collaborator

Choose a reason for hiding this comment

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

If one scan is named foo and the other is foobar, depending on the order they're returned, it's possible this would return the wrong PVC, wouldn't it?

if strings.Contains(pvc.Name, scanName) {
scanPVC = pvc
break
}
}

if scanPVC == nil {
return fmt.Errorf("no PVC found for scan %s", scanName)
}

// Verify PVC has the correct storageClassName
if scanPVC.Spec.StorageClassName == nil {
return fmt.Errorf("PVC %s has nil storageClassName", scanPVC.Name)
}
if *scanPVC.Spec.StorageClassName != expectedStorageClassName {
return fmt.Errorf("expected PVC storageClassName to be %s, got %s", expectedStorageClassName, *scanPVC.Spec.StorageClassName)
}

// Verify PVC has the correct accessModes
if len(scanPVC.Spec.AccessModes) == 0 {
return fmt.Errorf("PVC %s has no access modes", scanPVC.Name)
}

found := false
for _, mode := range scanPVC.Spec.AccessModes {
if mode == expectedAccessMode {
found = true
break
}
}

if !found {
return fmt.Errorf("expected PVC to have access mode %s, got %v", expectedAccessMode, scanPVC.Spec.AccessModes)
}

log.Printf("Successfully verified PVC %s has storageClassName=%s and accessModes=%v\n",
scanPVC.Name, *scanPVC.Spec.StorageClassName, scanPVC.Spec.AccessModes)

return nil
}
84 changes: 84 additions & 0 deletions tests/e2e/parallel/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5308,3 +5308,87 @@ func TestRuleVariableAnnotation(t *testing.T) {
})
}
}

// Verifies that access modes and Storage class are configurable through ComplianceSuite and ComplianceScan
func TestScanWithCustomStorageClass(t *testing.T) {
t.Parallel()
f := framework.Global

scanName := framework.GetObjNameFromTest(t)
suiteName := scanName + "-suite"
storageClassName := scanName + "-gold"

// Get the default storage class provisioner for our custom storage class
defaultProvisioner, err := f.GetDefaultStorageClassProvisioner()
if err != nil {
t.Skipf("skipping test: no default storage class provisioner available: %s", err)
}

// Create custom StorageClass named "gold"
customStorageClass, err := f.CreateCustomStorageClass(storageClassName, defaultProvisioner)
if err != nil {
t.Fatalf("failed to create custom storage class object %s: %s", storageClassName, err)
}
err = f.Client.Create(context.TODO(), customStorageClass, nil)
if err != nil {
t.Fatalf("failed to create custom storage class %s: %s", storageClassName, err)
}
defer f.Client.Delete(context.TODO(), customStorageClass)

// Create ComplianceSuite with custom storage configuration
testSuite := &compv1alpha1.ComplianceSuite{
ObjectMeta: metav1.ObjectMeta{
Name: suiteName,
Namespace: f.OperatorNamespace,
},
Spec: compv1alpha1.ComplianceSuiteSpec{
ComplianceSuiteSettings: compv1alpha1.ComplianceSuiteSettings{
AutoApplyRemediations: false,
},
Scans: []compv1alpha1.ComplianceScanSpecWrapper{
{
Name: scanName,
ComplianceScanSpec: compv1alpha1.ComplianceScanSpec{
ScanType: compv1alpha1.ScanTypeNode,
ContentImage: contentImagePath,
Profile: "xccdf_org.ssgproject.content_profile_moderate",
Content: framework.RhcosContentFile,
Rule: "xccdf_org.ssgproject.content_rule_no_netrc_files",
NodeSelector: map[string]string{
"node-role.kubernetes.io/worker": "",
},
ComplianceScanSettings: compv1alpha1.ComplianceScanSettings{
Debug: true,
RawResultStorage: compv1alpha1.RawResultStorageSettings{
StorageClassName: &storageClassName,
PVAccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
},
},
},
},
},
},
}

err = f.Client.Create(context.TODO(), testSuite, nil)
if err != nil {
t.Fatalf("failed to create compliance suite: %s", err)
}
defer f.Client.Delete(context.TODO(), testSuite)

// Wait for the scan to complete
err = f.WaitForSuiteScansStatus(f.OperatorNamespace, suiteName, compv1alpha1.PhaseDone, compv1alpha1.ResultCompliant)
if err != nil {
t.Fatalf("scan did not complete successfully: %s", err)
}

// Verify PVC has correct storageClassName and accessModes
err = f.AssertScanPVCHasStorageConfig(scanName, f.OperatorNamespace, storageClassName, corev1.ReadWriteOnce)
if err != nil {
t.Fatalf("PVC verification failed: %s", err)
}

t.Logf("Successfully verified scan %s has custom storage configuration", scanName)
}
Loading