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
116 changes: 116 additions & 0 deletions e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,119 @@ func TestProfileRemediations(t *testing.T) {
t.Logf("Warning: Failed to wait for scan cleanup for binding %s: %s", bindingName, err)
}
}

// TestNamespaceExemptionVariables tests the namespace exemption logic for
// resource limit checks. This test validates that:
// 1. Workloads without resource limits in exempted namespaces pass the check
// 2. The exemption variables work correctly for DaemonSet, Deployment, and StatefulSet
func TestNamespaceExemptionVariables(t *testing.T) {
// Skip if test type doesn't include platform tests
if tc.TestType != "platform" && tc.TestType != "all" {
t.Skipf("Skipping namespace exemption test: -test-type is %s", tc.TestType)
}

c, err := helpers.GenerateKubeConfig()
if err != nil {
t.Fatalf("Failed to generate kube config: %s", err)
}

// Test namespace names
testNamespaces := []string{
"ns-76797-test-1",
"ns-76797-test-2",
}

// Create test namespaces
for _, ns := range testNamespaces {
err = createNamespace(c, ns)
if err != nil {
t.Fatalf("Failed to create test namespace %s: %s", ns, err)
}
t.Logf("Created test namespace: %s", ns)
}

// Cleanup namespaces at the end
defer func() {
for _, ns := range testNamespaces {
deleteNamespace(c, ns)
}
}()

// Create workloads without resource limits in test namespaces
err = createTestWorkloadsWithoutLimits(c, testNamespaces[0])
if err != nil {
t.Fatalf("Failed to create test workloads: %s", err)
}
t.Logf("Created test workloads without resource limits in %s", testNamespaces[0])

// Wait for workloads to be created
time.Sleep(5 * time.Second)

// Build regex pattern to exempt test namespaces
// Pattern matches both test namespaces
exemptionPattern := "^ns-76797-test-.*$"

// Create TailoredProfile with namespace exemption variables
tailoredProfileName := "ns-exemption-test-profile"
err = createTailoredProfileWithExemptions(tc, c, tailoredProfileName, exemptionPattern)
if err != nil {
t.Fatalf("Failed to create tailored profile with exemptions: %s", err)
}
t.Logf("Created TailoredProfile: %s with exemption pattern: %s", tailoredProfileName, exemptionPattern)

// Create scan binding for the tailored profile
bindingName := "ns-exemption-scan-binding"
err = helpers.CreateScanBinding(c, tc, bindingName, tailoredProfileName, "TailoredProfile", "default")
if err != nil {
t.Fatalf("Failed to create scan binding: %s", err)
}
t.Logf("Created ScanSettingBinding: %s", bindingName)

// Wait for compliance suite to complete
err = helpers.WaitForComplianceSuite(tc, c, bindingName)
if err != nil {
t.Fatalf("Failed to wait for compliance suite: %s", err)
}

// Get scan results
results, err := helpers.CreateResultMap(tc, c, bindingName)
if err != nil {
t.Fatalf("Failed to create result map: %s", err)
}

// Verify that resource limit rules PASS because namespaces are exempted
expectedRules := map[string]string{
"resource-requests-limits-in-daemonset": "PASS",
"resource-requests-limits-in-deployment": "PASS",
"resource-requests-limits-in-statefulset": "PASS",
}

var failures []string
for ruleName, expectedResult := range expectedRules {
// Find the actual result - the result name might include scan name prefix
actualResult := findRuleResult(results, ruleName)
if actualResult == "" {
failures = append(failures, fmt.Sprintf("Rule %s not found in scan results", ruleName))
continue
}

if actualResult != expectedResult {
failures = append(failures,
fmt.Sprintf("Rule %s: expected %s, got %s", ruleName, expectedResult, actualResult))
} else {
t.Logf("Rule %s: %s (namespace exemption working correctly)", ruleName, actualResult)
}
}

// Save results for debugging
err = helpers.SaveResultAsYAML(tc, results, "namespace-exemption-test-results.yaml")
if err != nil {
t.Logf("Warning: Failed to save test results: %s", err)
}

if len(failures) > 0 {
t.Fatalf("Namespace exemption test failed:\n%v", failures)
}

t.Log("Namespace exemption test passed successfully - all exempted workloads passed the resource limit checks")
}
123 changes: 123 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package ocp4e2e

import (
"context"
"strings"

cmpv1alpha1 "github.com/ComplianceAsCode/compliance-operator/pkg/apis/compliance/v1alpha1"
"github.com/ComplianceAsCode/ocp4e2e/config"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
dynclient "sigs.k8s.io/controller-runtime/pkg/client"
)

// RuleTest is the definition of the structure rule-specific e2e tests should have.
type RuleTest struct {
DefaultResult interface{} `yaml:"default_result"`
Expand All @@ -13,3 +25,114 @@ type RuleTestResults struct {

func init() {
}

// createTailoredProfileWithExemptions creates a TailoredProfile for namespace exemption testing.
func createTailoredProfileWithExemptions(tc *config.TestConfig, c dynclient.Client, name, exemptionPattern string) error {
tp := &cmpv1alpha1.TailoredProfile{
ObjectMeta: metav1.ObjectMeta{
Name: name, Namespace: tc.OperatorNamespace.Namespace,
Annotations: map[string]string{"compliance.openshift.io/product-type": "Platform"},
},
Spec: cmpv1alpha1.TailoredProfileSpec{
Title: "Namespace Exemption Test Profile",
Description: "Test profile for validating namespace exemption variables",
EnableRules: []cmpv1alpha1.RuleReferenceSpec{
{Name: "ocp4-resource-requests-limits-in-daemonset"},
{Name: "ocp4-resource-requests-limits-in-deployment"},
{Name: "ocp4-resource-requests-limits-in-statefulset"},
},
SetValues: []cmpv1alpha1.VariableValueSpec{
{Name: "ocp4-var-daemonset-limit-namespaces-exempt-regex", Value: exemptionPattern},
{Name: "ocp4-var-deployment-limit-namespaces-exempt-regex", Value: exemptionPattern},
{Name: "ocp4-var-statefulset-limit-namespaces-exempt-regex", Value: exemptionPattern},
},
},
}
return c.Create(context.TODO(), tp)
}

// createTestWorkloadsWithoutLimits creates test workloads without resource limits.
func createTestWorkloadsWithoutLimits(c dynclient.Client, namespace string) error {
ctx := context.TODO()

workloads := []dynclient.Object{
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "test-deployment-no-limits", Namespace: namespace},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{
Name: "nginx", Image: "registry.access.redhat.com/ubi8/ubi-minimal:latest",
Command: []string{"/bin/sh", "-c", "sleep infinity"},
}}},
},
},
},
&appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "test-daemonset-no-limits", Namespace: namespace},
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "nginx", Image: "registry.access.redhat.com/ubi8/ubi-minimal:latest",
Command: []string{"/bin/sh", "-c", "sleep infinity"},
}},
Tolerations: []corev1.Toleration{{Operator: corev1.TolerationOpExists}},
},
},
},
},
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "test-statefulset-no-limits", Namespace: namespace},
Spec: appsv1.StatefulSetSpec{
Replicas: int32Ptr(1), ServiceName: "test",
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{
Name: "nginx", Image: "registry.access.redhat.com/ubi8/ubi-minimal:latest",
Command: []string{"/bin/sh", "-c", "sleep infinity"},
}}},
},
},
},
}

for _, w := range workloads {
if err := c.Create(ctx, w); err != nil {
return err
}
}
return nil
}

// createNamespace creates a namespace.
func createNamespace(c dynclient.Client, name string) error {
return c.Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}})
}

// deleteNamespace deletes a namespace.
func deleteNamespace(c dynclient.Client, name string) error {
c.Delete(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}})
return nil
}

// findRuleResult searches for a rule result by partial name match.
func findRuleResult(results map[string]string, ruleName string) string {
if result, exists := results[ruleName]; exists {
return result
}
for resultName, resultValue := range results {
if strings.Contains(resultName, ruleName) {
return resultValue
}
}
return ""
}

// int32Ptr returns a pointer to an int32 value.
func int32Ptr(i int32) *int32 { return &i }