Skip to content
Merged
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
11 changes: 8 additions & 3 deletions test/e2e/features/namespace.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ Feature: Namespace PSA Management

As an OLM user, when I install an operator that declares PSA requirements
via the suggested-namespace-template CSV annotation, operator-controller
should create a managed namespace with PSA labels applied.
should create a managed namespace with PSA labels applied and leave
user-provided namespaces unmanaged.

Background:
Given OLM is available
Expand Down Expand Up @@ -30,13 +31,15 @@ Feature: Namespace PSA Management
"""
Then ClusterExtension is rolled out
And ClusterExtension is available
And namespace "${PACKAGE:test}-system" is managed by OLM
And namespace "${PACKAGE:test}-system" has labels
| key | value |
| pod-security.kubernetes.io/enforce | privileged |
| pod-security.kubernetes.io/audit | privileged |
| pod-security.kubernetes.io/warn | privileged |
| e2e.olm.operatorframework.io/template | applied |

Scenario: User-provided namespace does not get PSA labels
Scenario: User-provided namespace remains unmanaged
Given namespace "${TEST_NAMESPACE}" is available
And a catalog "test" with packages:
| package | version | channel | replaces | contents |
Expand All @@ -59,4 +62,6 @@ Feature: Namespace PSA Management
"""
Then ClusterExtension is rolled out
And ClusterExtension is available
And namespace "${TEST_NAMESPACE}" does not have label "pod-security.kubernetes.io/enforce"
# Use a custom template label because other cluster controllers may apply PSA labels.
And namespace "${TEST_NAMESPACE}" does not have label "e2e.olm.operatorframework.io/template"
And namespace "${TEST_NAMESPACE}" has no owner references
52 changes: 52 additions & 0 deletions test/e2e/steps/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ func RegisterSteps(sc *godog.ScenarioContext) {
sc.Step(`^(?i)ValidatingAdmissionPolicy "([^"]+)" is active$`, ValidatingAdmissionPolicyIsActive)

sc.Step(`^(?i)namespace "([^"]+)" has labels$`, NamespaceHasLabels)
sc.Step(`^(?i)namespace "([^"]+)" is managed by OLM$`, NamespaceIsManagedByOLM)
sc.Step(`^(?i)namespace "([^"]+)" does not have label "([^"]+)"$`, NamespaceDoesNotHaveLabel)
sc.Step(`^(?i)namespace "([^"]+)" has no owner references$`, NamespaceHasNoOwnerReferences)

sc.Step(`^(?i)operator "([^"]+)" target namespace is "([^"]+)"$`, OperatorTargetNamespace)
sc.Step(`^(?i)Prometheus metrics are returned in the response$`, PrometheusMetricsAreReturned)
Expand Down Expand Up @@ -2513,6 +2515,56 @@ func NamespaceDoesNotHaveLabel(ctx context.Context, nsName string, labelKey stri
return nil
}

// NamespaceIsManagedByOLM waits for a namespace to be owned by the extension's active ClusterObjectSet.
func NamespaceIsManagedByOLM(ctx context.Context, nsName string) error {
sc := scenarioCtx(ctx)
nsName = substituteScenarioVars(nsName, sc)

waitFor(ctx, func() bool {
revision, err := latestActiveRevisionForExtension(sc.clusterExtensionName)
if err != nil {
logger.V(1).Error(err, "failed to get active revision", "extension", sc.clusterExtensionName)
return false
}
out, err := k8sClient(ctx, "get", "namespace", nsName, "-o", "json")
if err != nil {
return false
}
var ns corev1.Namespace
if err := json.Unmarshal([]byte(out), &ns); err != nil {
return false
}
owner := metav1.GetControllerOf(&ns)
if owner == nil || owner.APIVersion != ocv1.GroupVersion.String() ||
owner.Kind != ocv1.ClusterObjectSetKind || owner.Name != revision.Name || owner.UID != revision.UID {
logger.V(1).Info("Namespace is not yet owned by the extension's active revision",
"namespace", nsName, "revision", revision.Name, "owner", owner)
return false
}
return true
})
return nil
}

// NamespaceHasNoOwnerReferences verifies a namespace remains unmanaged after installation.
func NamespaceHasNoOwnerReferences(ctx context.Context, nsName string) error {
sc := scenarioCtx(ctx)
nsName = substituteScenarioVars(nsName, sc)

out, err := k8sClient(ctx, "get", "namespace", nsName, "-o", "json")
if err != nil {
return fmt.Errorf("failed to get namespace %q: %w", nsName, err)
}
var obj unstructured.Unstructured
if err := json.Unmarshal([]byte(out), &obj); err != nil {
return fmt.Errorf("failed to unmarshal namespace: %w", err)
}
if refs := obj.GetOwnerReferences(); len(refs) != 0 {
return fmt.Errorf("namespace %q has unexpected owner references: %v", nsName, refs)
}
return nil
}

// nestedString traverses a nested map[string]interface{} by the given keys
// and returns the leaf value as a string.
func nestedString(obj map[string]interface{}, keys ...string) (string, bool) {
Expand Down
5 changes: 3 additions & 2 deletions test/internal/catalog/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ func WithCSVAnnotation(key, value string) BundleOption {
}
}

// WithNSTemplate adds a suggested namespace template annotation to the CSV with the specified PSA level.
// WithNSTemplate adds a suggested namespace template annotation to the CSV with the specified PSA level
// and a custom label to verify template application independently of cluster PSA controllers.
func WithNSTemplate(psaLevel string) BundleOption {
template := fmt.Sprintf(`{"apiVersion":"v1","kind":"Namespace","metadata":{"labels":{"pod-security.kubernetes.io/enforce":"%s","pod-security.kubernetes.io/audit":"%s","pod-security.kubernetes.io/warn":"%s"}}}`, psaLevel, psaLevel, psaLevel)
template := fmt.Sprintf(`{"apiVersion":"v1","kind":"Namespace","metadata":{"labels":{"e2e.olm.operatorframework.io/template":"applied","pod-security.kubernetes.io/enforce":"%s","pod-security.kubernetes.io/audit":"%s","pod-security.kubernetes.io/warn":"%s"}}}`, psaLevel, psaLevel, psaLevel)
return WithCSVAnnotation("operatorframework.io/suggested-namespace-template", template)
}

Expand Down
Loading