@@ -2,87 +2,195 @@ package helpers
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
6
+ "time"
5
7
8
+ //nolint:staticcheck // ST1001: dot-imports for readability
9
+ . "github.com/onsi/ginkgo/v2"
6
10
//nolint:staticcheck // ST1001: dot-imports for readability
7
11
. "github.com/onsi/gomega"
8
12
9
13
corev1 "k8s.io/api/core/v1"
10
14
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"
11
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12
19
"k8s.io/apimachinery/pkg/util/rand"
20
+ "sigs.k8s.io/controller-runtime/pkg/client"
13
21
14
- ocv1 "github.com/operator-framework/operator-controller/api/v1"
22
+ olmv1 "github.com/operator-framework/operator-controller/api/v1"
15
23
16
24
"github/operator-framework-operator-controller/openshift/tests-extension/pkg/env"
17
25
)
18
26
19
- const openshiftOperatorsNs = "openshift-operators"
20
-
21
27
// CreateClusterExtension creates a ServiceAccount, ClusterRoleBinding, and ClusterExtension using typed APIs.
22
28
// 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 ()) {
24
30
ctx := context .TODO ()
25
31
k8sClient := env .Get ().K8sClient
26
- unique := rand .String (8 )
32
+ unique := rand .String (4 )
27
33
28
34
saName := "install-test-sa-" + unique
29
35
crbName := "install-test-crb-" + unique
30
36
ceName := "install-test-ce-" + unique
31
37
32
38
// 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 )
40
44
41
45
// 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 object in the openshift-operators namespace.
64
+ func NewServiceAccount (name , namespace string ) * corev1.ServiceAccount {
65
+ return & corev1.ServiceAccount {
43
66
ObjectMeta : metav1.ObjectMeta {
44
- Name : crbName ,
67
+ Name : name ,
68
+ Namespace : namespace ,
45
69
},
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 },
46
77
RoleRef : rbacv1.RoleRef {
47
78
APIGroup : "rbac.authorization.k8s.io" ,
48
79
Kind : "ClusterRole" ,
49
- Name : "cluster-admin" ,
80
+ Name : roleName ,
50
81
},
51
82
Subjects : []rbacv1.Subject {{
52
83
Kind : "ServiceAccount" ,
53
84
Name : saName ,
54
- Namespace : openshiftOperatorsNs ,
85
+ Namespace : namespace ,
55
86
}},
56
87
}
57
- Expect ( k8sClient . Create ( ctx , crb )). To ( Succeed (), "failed to create ClusterRoleBinding" )
88
+ }
58
89
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 {
67
97
Name : saName ,
68
98
},
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 ,
73
103
Version : version ,
74
104
Selector : & metav1.LabelSelector {},
75
- UpgradeConstraintPolicy : ocv1 .UpgradeConstraintPolicyCatalogProvided ,
105
+ UpgradeConstraintPolicy : olmv1 .UpgradeConstraintPolicyCatalogProvided ,
76
106
},
77
107
},
78
108
},
79
109
}
80
- Expect ( k8sClient . Create ( ctx , ce )). To ( Succeed (), "failed to create ClusterExtension" )
110
+ }
81
111
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 lingering 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 (), "Lingering 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
+ }
87
175
}
88
176
}
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