@@ -2,87 +2,195 @@ package helpers
22
33import (
44 "context"
5+ "fmt"
6+ "time"
57
8+ //nolint:staticcheck // ST1001: dot-imports for readability
9+ . "github.com/onsi/ginkgo/v2"
610 //nolint:staticcheck // ST1001: dot-imports for readability
711 . "github.com/onsi/gomega"
812
913 corev1 "k8s.io/api/core/v1"
1014 rbacv1 "k8s.io/api/rbac/v1"
15+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
16+ "k8s.io/apimachinery/pkg/api/errors"
17+ "k8s.io/apimachinery/pkg/api/meta"
1118 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1219 "k8s.io/apimachinery/pkg/util/rand"
20+ "sigs.k8s.io/controller-runtime/pkg/client"
1321
14- ocv1 "github.com/operator-framework/operator-controller/api/v1"
22+ olmv1 "github.com/operator-framework/operator-controller/api/v1"
1523
1624 "github/operator-framework-operator-controller/openshift/tests-extension/pkg/env"
1725)
1826
19- const openshiftOperatorsNs = "openshift-operators"
20-
2127// CreateClusterExtension creates a ServiceAccount, ClusterRoleBinding, and ClusterExtension using typed APIs.
2228// It returns the unique suffix and a cleanup function.
23- func CreateClusterExtension (packageName , version string ) (string , func ()) {
29+ func CreateClusterExtension (packageName , version , namespace string ) (string , func ()) {
2430 ctx := context .TODO ()
2531 k8sClient := env .Get ().K8sClient
26- unique := rand .String (8 )
32+ unique := rand .String (4 )
2733
2834 saName := "install-test-sa-" + unique
2935 crbName := "install-test-crb-" + unique
3036 ceName := "install-test-ce-" + unique
3137
3238 // 1. Create ServiceAccount
33- sa := & corev1.ServiceAccount {
34- ObjectMeta : metav1.ObjectMeta {
35- Name : saName ,
36- Namespace : openshiftOperatorsNs ,
37- },
38- }
39- Expect (k8sClient .Create (ctx , sa )).To (Succeed (), "failed to create ServiceAccount" )
39+ sa := NewServiceAccount (saName , namespace )
40+ Expect (k8sClient .Create (ctx , sa )).To (Succeed (),
41+ "failed to create ServiceAccount" )
42+ By ("ensuring ServiceAccount is available before proceeding" )
43+ ExpectServiceAccountExists (ctx , saName , namespace )
4044
4145 // 2. Create ClusterRoleBinding
42- crb := & rbacv1.ClusterRoleBinding {
46+ crb := NewClusterRoleBinding (crbName , "cluster-admin" , saName , namespace )
47+ Expect (k8sClient .Create (ctx , crb )).To (Succeed (), "failed to create ClusterRoleBinding" )
48+ By ("ensuring ClusterRoleBinding is available before proceeding" )
49+ ExpectClusterRoleBindingExists (ctx , crbName )
50+
51+ // 3. Create ClusterExtension
52+ ce := NewClusterExtensionObject (packageName , version , ceName , saName , namespace )
53+ Expect (k8sClient .Create (ctx , ce )).To (Succeed (), "failed to create ClusterExtension" )
54+
55+ // Cleanup closure
56+ return ceName , func () {
57+ _ = k8sClient .Delete (ctx , ce )
58+ _ = k8sClient .Delete (ctx , crb )
59+ _ = k8sClient .Delete (ctx , sa )
60+ }
61+ }
62+
63+ // NewServiceAccount creates a new ServiceAccount.
64+ func NewServiceAccount (name , namespace string ) * corev1.ServiceAccount {
65+ return & corev1.ServiceAccount {
4366 ObjectMeta : metav1.ObjectMeta {
44- Name : crbName ,
67+ Name : name ,
68+ Namespace : namespace ,
4569 },
70+ }
71+ }
72+
73+ // NewClusterRoleBinding creates a new ClusterRoleBinding object that binds a ClusterRole to a ServiceAccount.
74+ func NewClusterRoleBinding (name , roleName , saName , namespace string ) * rbacv1.ClusterRoleBinding {
75+ return & rbacv1.ClusterRoleBinding {
76+ ObjectMeta : metav1.ObjectMeta {Name : name },
4677 RoleRef : rbacv1.RoleRef {
4778 APIGroup : "rbac.authorization.k8s.io" ,
4879 Kind : "ClusterRole" ,
49- Name : "cluster-admin" ,
80+ Name : roleName ,
5081 },
5182 Subjects : []rbacv1.Subject {{
5283 Kind : "ServiceAccount" ,
5384 Name : saName ,
54- Namespace : openshiftOperatorsNs ,
85+ Namespace : namespace ,
5586 }},
5687 }
57- Expect ( k8sClient . Create ( ctx , crb )). To ( Succeed (), "failed to create ClusterRoleBinding" )
88+ }
5889
59- // 3. Create ClusterExtension
60- ce := & ocv1.ClusterExtension {
61- ObjectMeta : metav1.ObjectMeta {
62- Name : ceName ,
63- },
64- Spec : ocv1.ClusterExtensionSpec {
65- Namespace : openshiftOperatorsNs ,
66- ServiceAccount : ocv1.ServiceAccountReference {
90+ // NewClusterExtensionObject creates a new ClusterExtension object with the specified package, version, name, and ServiceAccount.
91+ func NewClusterExtensionObject (pkg , version , ceName , saName , namespace string ) * olmv1.ClusterExtension {
92+ return & olmv1.ClusterExtension {
93+ ObjectMeta : metav1.ObjectMeta {Name : ceName },
94+ Spec : olmv1.ClusterExtensionSpec {
95+ Namespace : namespace ,
96+ ServiceAccount : olmv1.ServiceAccountReference {
6797 Name : saName ,
6898 },
69- Source : ocv1 .SourceConfig {
70- SourceType : ocv1 .SourceTypeCatalog ,
71- Catalog : & ocv1 .CatalogFilter {
72- PackageName : packageName ,
99+ Source : olmv1 .SourceConfig {
100+ SourceType : olmv1 .SourceTypeCatalog ,
101+ Catalog : & olmv1 .CatalogFilter {
102+ PackageName : pkg ,
73103 Version : version ,
74104 Selector : & metav1.LabelSelector {},
75- UpgradeConstraintPolicy : ocv1 .UpgradeConstraintPolicyCatalogProvided ,
105+ UpgradeConstraintPolicy : olmv1 .UpgradeConstraintPolicyCatalogProvided ,
76106 },
77107 },
78108 },
79109 }
80- Expect ( k8sClient . Create ( ctx , ce )). To ( Succeed (), "failed to create ClusterExtension" )
110+ }
81111
82- // Cleanup closure
83- return ceName , func () {
84- _ = k8sClient .Delete (ctx , ce )
85- _ = k8sClient .Delete (ctx , crb )
86- _ = k8sClient .Delete (ctx , sa )
112+ // ExpectClusterExtensionToBeInstalled checks that the ClusterExtension has both Progressing=True and Installed=True.
113+ func ExpectClusterExtensionToBeInstalled (ctx context.Context , name string ) {
114+ k8sClient := env .Get ().K8sClient
115+ Eventually (func (g Gomega ) {
116+ var ext olmv1.ClusterExtension
117+ err := k8sClient .Get (ctx , client.ObjectKey {Name : name }, & ext )
118+ g .Expect (err ).ToNot (HaveOccurred (), fmt .Sprintf ("failed to get ClusterExtension %q" , name ))
119+
120+ conditions := ext .Status .Conditions
121+ g .Expect (conditions ).NotTo (BeEmpty (), fmt .Sprintf ("ClusterExtension %q has empty status.conditions" , name ))
122+
123+ progressing := meta .FindStatusCondition (conditions , string (olmv1 .TypeProgressing ))
124+ g .Expect (progressing ).ToNot (BeNil (), "Progressing condition not found" )
125+ g .Expect (progressing .Status ).To (Equal (metav1 .ConditionTrue ), "Progressing should be True" )
126+
127+ installed := meta .FindStatusCondition (conditions , string (olmv1 .TypeInstalled ))
128+ g .Expect (installed ).ToNot (BeNil (), "Installed condition not found" )
129+ g .Expect (installed .Status ).To (Equal (metav1 .ConditionTrue ), "Installed should be True" )
130+ }).WithTimeout (5 * time .Minute ).WithPolling (1 * time .Second ).Should (Succeed ())
131+ }
132+
133+ // EnsureCleanupClusterExtension attempts to delete any ClusterExtension and a specified CRD
134+ // that might be left over from previous test runs. This helps prevent conflicts in serial tests.
135+ func EnsureCleanupClusterExtension (ctx context.Context , packageName , crdName string ) {
136+ k8sClient := env .Get ().K8sClient
137+
138+ // 1. Clean up any ClusterExtensions related to this test/package
139+ ceList := & olmv1.ClusterExtensionList {}
140+ // List all ClusterExtensions, then filter in code by packageName
141+ if err := k8sClient .List (ctx , ceList ); err == nil {
142+ for _ , ce := range ceList .Items {
143+ if ce .Spec .Source .Catalog .PackageName == packageName {
144+ By (fmt .Sprintf ("deleting ClusterExtension %s (package: %s)" , ce .Name , packageName ))
145+ propagationPolicy := metav1 .DeletePropagationForeground
146+ deleteOpts := & client.DeleteOptions {PropagationPolicy : & propagationPolicy }
147+ if err := k8sClient .Delete (ctx , & ce , deleteOpts ); err != nil && ! errors .IsNotFound (err ) {
148+ fmt .Fprintf (GinkgoWriter , "Warning: Failed to delete remaning ClusterExtension %s: %v\n " , ce .Name , err )
149+ }
150+ Eventually (func () bool {
151+ err := k8sClient .Get (ctx , client.ObjectKey {Name : ce .Name }, & olmv1.ClusterExtension {})
152+ return errors .IsNotFound (err )
153+ }).WithTimeout (1 * time .Minute ).WithPolling (2 * time .Second ).Should (BeTrue (), "Cleanup ClusterExtension %s failed to delete" , ce .Name )
154+ }
155+ }
156+ } else if ! errors .IsNotFound (err ) {
157+ fmt .Fprintf (GinkgoWriter , "Warning: Failed to list ClusterExtensions during cleanup: %v\n " , err )
158+ }
159+
160+ // 2. Clean up specific operator-created CRD if it exists
161+ if crdName != "" {
162+ crd := & apiextensionsv1.CustomResourceDefinition {}
163+ if err := k8sClient .Get (ctx , client.ObjectKey {Name : crdName }, crd ); err == nil {
164+ By (fmt .Sprintf ("deleting CRD %s" , crdName ))
165+ if err := k8sClient .Delete (ctx , crd ); err != nil && ! errors .IsNotFound (err ) {
166+ fmt .Fprintf (GinkgoWriter , "Warning: Failed to delete lingering CRD %s: %v\n " , crdName , err )
167+ }
168+ Eventually (func () bool {
169+ err := k8sClient .Get (ctx , client.ObjectKey {Name : crdName }, & apiextensionsv1.CustomResourceDefinition {})
170+ return errors .IsNotFound (err )
171+ }).WithTimeout (1 * time .Minute ).WithPolling (2 * time .Second ).Should (BeTrue (), "Lingering CRD %s failed to delete" , crdName )
172+ } else if ! errors .IsNotFound (err ) {
173+ fmt .Fprintf (GinkgoWriter , "Warning: Failed to get CRD %s during cleanup: %v\n " , crdName , err )
174+ }
87175 }
88176}
177+
178+ // ExpectServiceAccountExists waits for a ServiceAccount to be available and visible to the client.
179+ func ExpectServiceAccountExists (ctx context.Context , name , namespace string ) {
180+ k8sClient := env .Get ().K8sClient
181+ sa := & corev1.ServiceAccount {}
182+ Eventually (func (g Gomega ) {
183+ err := k8sClient .Get (ctx , client.ObjectKey {Name : name , Namespace : namespace }, sa )
184+ g .Expect (err ).ToNot (HaveOccurred (), fmt .Sprintf ("failed to get ServiceAccount %q/%q: %v" , namespace , name , err ))
185+ }).WithTimeout (10 * time .Second ).WithPolling (1 * time .Second ).Should (Succeed (), "ServiceAccount %q/%q did not become visible within timeout" , namespace , name )
186+ }
187+
188+ // ExpectClusterRoleBindingExists waits for a ClusterRoleBinding to be available and visible to the client.
189+ func ExpectClusterRoleBindingExists (ctx context.Context , name string ) {
190+ k8sClient := env .Get ().K8sClient
191+ crb := & rbacv1.ClusterRoleBinding {}
192+ Eventually (func (g Gomega ) {
193+ err := k8sClient .Get (ctx , client.ObjectKey {Name : name }, crb )
194+ g .Expect (err ).ToNot (HaveOccurred (), fmt .Sprintf ("failed to get ClusterRoleBinding %q: %v" , name , err ))
195+ }).WithTimeout (10 * time .Second ).WithPolling (1 * time .Second ).Should (Succeed (), "ClusterRoleBinding %q did not become visible within timeout" , name )
196+ }
0 commit comments