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
66 changes: 66 additions & 0 deletions test/e2e/features/uninstall.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Feature: Uninstall ClusterExtension

As an OLM user I would like to uninstall a cluster extension.

Background:
Given OLM is available
And ClusterCatalog "test" serves bundles
And ServiceAccount "olm-sa" with needed permissions is available in ${TEST_NAMESPACE}
And ClusterExtension is applied
"""
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
name: ${NAME}
spec:
namespace: ${TEST_NAMESPACE}
serviceAccount:
name: olm-sa
source:
sourceType: Catalog
catalog:
packageName: test
selector:
matchLabels:
"olm.operatorframework.io/metadata.name": test-catalog
"""
And bundle "test-operator.1.2.0" is installed in version "1.2.0"
# Ensure the bundle resources exist before initiating the deletion process and checking
# that they no longer exist to avoid false positives (i.e. if they never existed, checking that they don't exist
# will succeed)
And resource "networkpolicy/test-operator-network-policy" exists
And resource "configmap/test-configmap" exists
And resource "deployment/test-operator" exists
# Note: The names of these resources are derived from the permissions contained in the clusterroles
# If those permissions change due to changes in the bundle, the names of these resources will also change
# causing a failure here
And resource "clusterrole/testoperator.v1.2.-37mym6pni2xxmai9n7fmhtbn9i348lx7o619rmf3ypio" exists
And resource "clusterrole/testoperator.v1.2.0-t88i5epjh8oxp4klplhjyrsekwcp92b27w03ayr1ku5" exists
And resource "clusterrolebinding/testoperator.v1.2.-37mym6pni2xxmai9n7fmhtbn9i348lx7o619rmf3ypio" exists
And resource "clusterrolebinding/testoperator.v1.2.0-t88i5epjh8oxp4klplhjyrsekwcp92b27w03ayr1ku5" exists

Scenario: Uninstall ClusterExtension
When resource "clusterextension/${NAME}" is removed
Then resource "networkpolicy/test-operator-network-policy" is eventually not found
And resource "configmap/test-configmap" is eventually not found
And resource "deployment/test-operator" is eventually not found
And resource "clusterrole/testoperator.v1.2.-37mym6pni2xxmai9n7fmhtbn9i348lx7o619rmf3ypio" is eventually not found
And resource "clusterrole/testoperator.v1.2.0-t88i5epjh8oxp4klplhjyrsekwcp92b27w03ayr1ku5" is eventually not found
And resource "clusterrolebinding/testoperator.v1.2.-37mym6pni2xxmai9n7fmhtbn9i348lx7o619rmf3ypio" is eventually not found
And resource "clusterrolebinding/testoperator.v1.2.0-t88i5epjh8oxp4klplhjyrsekwcp92b27w03ayr1ku5" is eventually not found

Scenario: ClusterExtension resources are cleaned up even if the ServiceAccount is no longer present
# This scenario is especially important for GitOps where the ClusterExtension and related resources (namespace,
# service account, etc.) are deleted together
When resource "serviceaccount/olm-sa" is removed
# Ensure service account is gone before checking to ensure resources are cleaned up whether the service account
# and its permissions are present on the cluster or not
And resource "serviceaccount/olm-sa" is eventually not found
And resource "clusterextension/${NAME}" is removed
Then resource "networkpolicy/test-operator-network-policy" is eventually not found
And resource "configmap/test-configmap" is eventually not found
And resource "deployment/test-operator" is eventually not found
And resource "clusterrole/testoperator.v1.2.-37mym6pni2xxmai9n7fmhtbn9i348lx7o619rmf3ypio" is eventually not found
And resource "clusterrole/testoperator.v1.2.0-t88i5epjh8oxp4klplhjyrsekwcp92b27w03ayr1ku5" is eventually not found
And resource "clusterrolebinding/testoperator.v1.2.-37mym6pni2xxmai9n7fmhtbn9i348lx7o619rmf3ypio" is eventually not found
And resource "clusterrolebinding/testoperator.v1.2.0-t88i5epjh8oxp4klplhjyrsekwcp92b27w03ayr1ku5" is eventually not found
46 changes: 32 additions & 14 deletions test/e2e/steps/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func RegisterSteps(sc *godog.ScenarioContext) {
sc.Step(`^(?i)resource "([^"]+)" is installed$`, ResourceAvailable)
sc.Step(`^(?i)resource "([^"]+)" is available$`, ResourceAvailable)
sc.Step(`^(?i)resource "([^"]+)" is removed$`, ResourceRemoved)
sc.Step(`^(?i)resource "([^"]+)" is eventually not found$`, ResourceEventuallyNotFound)
sc.Step(`^(?i)resource "([^"]+)" exists$`, ResourceAvailable)
sc.Step(`^(?i)resource is applied$`, ResourceIsApplied)
sc.Step(`^(?i)resource "deployment/test-operator" reports as (not ready|ready)$`, MarkTestOperatorNotReady)
Expand Down Expand Up @@ -398,24 +399,25 @@ func ClusterExtensionRevisionIsArchived(ctx context.Context, revisionName string
func ResourceAvailable(ctx context.Context, resource string) error {
sc := scenarioCtx(ctx)
resource = substituteScenarioVars(resource, sc)
rtype, name, found := strings.Cut(resource, "/")
kind, name, found := strings.Cut(resource, "/")
if !found {
return fmt.Errorf("resource %s is not in the format <type>/<name>", resource)
return fmt.Errorf("resource %s is not in the format <kind>/<name>", resource)
}
waitFor(ctx, func() bool {
_, err := k8sClient("get", rtype, name, "-n", sc.namespace)
_, err := k8sClient("get", kind, name, "-n", sc.namespace)
return err == nil
})
return nil
}

func ResourceRemoved(ctx context.Context, resource string) error {
sc := scenarioCtx(ctx)
rtype, name, found := strings.Cut(resource, "/")
resource = substituteScenarioVars(resource, sc)
kind, name, found := strings.Cut(resource, "/")
if !found {
return fmt.Errorf("resource %s is not in the format <type>/<name>", resource)
return fmt.Errorf("resource %s is not in the format <kind>/<name>", resource)
}
yaml, err := k8sClient("get", rtype, name, "-n", sc.namespace, "-o", "yaml")
yaml, err := k8sClient("get", kind, name, "-n", sc.namespace, "-o", "yaml")
if err != nil {
return err
}
Expand All @@ -424,23 +426,38 @@ func ResourceRemoved(ctx context.Context, resource string) error {
return err
}
sc.removedResources = append(sc.removedResources, *obj)
_, err = k8sClient("delete", rtype, name, "-n", sc.namespace)
_, err = k8sClient("delete", kind, name, "-n", sc.namespace)
return err
}

func ResourceEventuallyNotFound(ctx context.Context, resource string) error {
sc := scenarioCtx(ctx)
resource = substituteScenarioVars(resource, sc)
kind, name, found := strings.Cut(resource, "/")
if !found {
return fmt.Errorf("resource %s is not in the format <kind>/<name>", resource)
}

require.Eventually(godog.T(ctx), func() bool {
obj, err := k8sClient("get", kind, name, "-n", sc.namespace, "--ignore-not-found", "-o", "yaml")
return err == nil && obj == ""
}, timeout, tick)
return nil
}

func ResourceMatches(ctx context.Context, resource string, requiredContentTemplate *godog.DocString) error {
sc := scenarioCtx(ctx)
resource = substituteScenarioVars(resource, sc)
rtype, name, found := strings.Cut(resource, "/")
kind, name, found := strings.Cut(resource, "/")
if !found {
return fmt.Errorf("resource %s is not in the format <type>/<name>", resource)
return fmt.Errorf("resource %s is not in the format <kind>/<name>", resource)
}
requiredContent, err := toUnstructured(substituteScenarioVars(requiredContentTemplate.Content, sc))
if err != nil {
return fmt.Errorf("failed to parse required resource yaml: %v", err)
}
waitFor(ctx, func() bool {
objJson, err := k8sClient("get", rtype, name, "-n", sc.namespace, "-o", "json")
objJson, err := k8sClient("get", kind, name, "-n", sc.namespace, "-o", "json")
if err != nil {
return false
}
Expand Down Expand Up @@ -468,12 +485,13 @@ func ResourceMatches(ctx context.Context, resource string, requiredContentTempla

func ResourceRestored(ctx context.Context, resource string) error {
sc := scenarioCtx(ctx)
rtype, name, found := strings.Cut(resource, "/")
resource = substituteScenarioVars(resource, sc)
kind, name, found := strings.Cut(resource, "/")
if !found {
return fmt.Errorf("resource %s is not in the format <type>/<name>", resource)
return fmt.Errorf("resource %s is not in the format <kind>/<name>", resource)
}
waitFor(ctx, func() bool {
yaml, err := k8sClient("get", rtype, name, "-n", sc.namespace, "-o", "yaml")
yaml, err := k8sClient("get", kind, name, "-n", sc.namespace, "-o", "yaml")
if err != nil {
return false
}
Expand All @@ -486,7 +504,7 @@ func ResourceRestored(ctx context.Context, resource string) error {
for i, removed := range sc.removedResources {
rct := removed.GetCreationTimestamp()
if removed.GetName() == obj.GetName() && removed.GetKind() == obj.GetKind() && rct.Before(&ct) {
switch rtype {
switch kind {
case "configmap":
if !reflect.DeepEqual(removed.Object["data"], obj.Object["data"]) {
return false
Expand Down