Skip to content

Commit 1c10730

Browse files
smarterclaytonecordell
authored andcommitted
clusteroperator: Report when OLM reaches "level" and check syncs
Cluster operators are expected to report the version of the payload they are included in once they are "deployed", and also to keep the cluster operator object created. Have the OLM operator keep CO up to date, report the payload version once it hits available, and use the count of successful syncs from the queueInformers as a probalistic measurement of "available" (i.e. is the operator able to retire syncs). A future change should add a "health over time" metric or a "has successfully synced all InstallPlans at least once" metric to replace the current estimation.
1 parent 176bf33 commit 1c10730

File tree

9 files changed

+259
-77
lines changed

9 files changed

+259
-77
lines changed

cmd/catalog/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,6 @@ func main() {
9696
http.Handle("/metrics", promhttp.Handler())
9797
go http.ListenAndServe(":8081", nil)
9898

99-
_, done := catalogOperator.Run(stopCh)
99+
_, done, _ := catalogOperator.Run(stopCh)
100100
<-done
101101
}

cmd/olm/main.go

Lines changed: 171 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"os"
8+
"reflect"
89
"strings"
910
"time"
1011

@@ -13,21 +14,20 @@ import (
1314
v1 "k8s.io/api/core/v1"
1415
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1516
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/runtime/schema"
18+
"k8s.io/apimachinery/pkg/util/wait"
19+
"k8s.io/client-go/discovery"
20+
"k8s.io/client-go/tools/clientcmd"
1621

1722
configv1 "github.com/openshift/api/config/v1"
1823
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
19-
clusteroperatorv1helpers "github.com/openshift/library-go/pkg/config/clusteroperator/v1helpers"
20-
operatorv1helpers "github.com/openshift/library-go/pkg/operator/v1helpers"
2124
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client"
2225
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
2326
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm"
2427
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
2528
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/signals"
2629
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
2730
olmversion "github.com/operator-framework/operator-lifecycle-manager/pkg/version"
28-
"k8s.io/apimachinery/pkg/runtime/schema"
29-
"k8s.io/client-go/discovery"
30-
"k8s.io/client-go/tools/clientcmd"
3131
)
3232

3333
const (
@@ -128,98 +128,198 @@ func main() {
128128
http.Handle("/metrics", promhttp.Handler())
129129
go http.ListenAndServe(":8081", nil)
130130

131-
ready, done := operator.Run(stopCh)
131+
ready, done, sync := operator.Run(stopCh)
132132
<-ready
133133

134134
if *writeStatusName != "" {
135-
opStatusGV := schema.GroupVersion{
136-
Group: "config.openshift.io",
137-
Version: "v1",
135+
monitorClusterStatus(sync, stopCh, opClient, configClient)
136+
}
137+
138+
<-done
139+
}
140+
141+
func monitorClusterStatus(syncCh chan error, stopCh <-chan struct{}, opClient operatorclient.ClientInterface, configClient configv1client.ConfigV1Interface) {
142+
var (
143+
syncs int
144+
successfulSyncs int
145+
hasClusterOperator bool
146+
)
147+
go wait.Until(func() {
148+
// slow poll until we see a cluster operator API, which could be never
149+
if !hasClusterOperator {
150+
opStatusGV := schema.GroupVersion{
151+
Group: "config.openshift.io",
152+
Version: "v1",
153+
}
154+
err := discovery.ServerSupportsVersion(opClient.KubernetesInterface().Discovery(), opStatusGV)
155+
if err != nil {
156+
log.Infof("ClusterOperator api not present, skipping update (%v)", err)
157+
time.Sleep(time.Minute)
158+
return
159+
}
160+
hasClusterOperator = true
138161
}
139-
err := discovery.ServerSupportsVersion(opClient.KubernetesInterface().Discovery(), opStatusGV)
140-
if err != nil {
141-
log.Infof("ClusterOperator api not present, skipping update (%v)", err)
142-
} else {
143-
existing, err := configClient.ClusterOperators().Get(*writeStatusName, metav1.GetOptions{})
144-
if k8serrors.IsNotFound(err) {
145-
log.Info("Existing operator status not found, creating")
146-
created, err := configClient.ClusterOperators().Create(&configv1.ClusterOperator{
147-
ObjectMeta: metav1.ObjectMeta{
148-
Name: *writeStatusName,
149-
},
150-
})
151-
if err != nil {
152-
log.Fatalf("ClusterOperator create failed: %v\n", err)
162+
163+
// Sample the sync channel and see whether we're successfully retiring syncs as a
164+
// proxy for "working" (we can't know when we hit level, but we can at least verify
165+
// we are seeing some syncs succeeding). Once we observe at least one successful
166+
// sync we can begin reporting available and level.
167+
select {
168+
case err, ok := <-syncCh:
169+
if !ok {
170+
// syncCh should only close if the Run() loop exits
171+
time.Sleep(5 * time.Second)
172+
log.Fatalf("Status sync channel closed but process did not exit in time")
173+
}
174+
syncs++
175+
if err == nil {
176+
successfulSyncs++
177+
}
178+
// grab any other sync events that have accumulated
179+
for len(syncCh) > 0 {
180+
if err := <-syncCh; err == nil {
181+
successfulSyncs++
153182
}
183+
syncs++
184+
}
185+
// if we haven't yet accumulated enough syncs, wait longer
186+
// TODO: replace these magic numbers with a better measure of syncs across all queueInformers
187+
if successfulSyncs < 5 || syncs < 10 {
188+
log.Printf("Waiting to observe more successful syncs")
189+
return
190+
}
191+
}
154192

155-
created.Status = configv1.ClusterOperatorStatus{
193+
// create the cluster operator in an initial state if it does not exist
194+
existing, err := configClient.ClusterOperators().Get(*writeStatusName, metav1.GetOptions{})
195+
if k8serrors.IsNotFound(err) {
196+
log.Info("Existing operator status not found, creating")
197+
created, createErr := configClient.ClusterOperators().Create(&configv1.ClusterOperator{
198+
ObjectMeta: metav1.ObjectMeta{
199+
Name: *writeStatusName,
200+
},
201+
Status: configv1.ClusterOperatorStatus{
156202
Conditions: []configv1.ClusterOperatorStatusCondition{
157203
configv1.ClusterOperatorStatusCondition{
158204
Type: configv1.OperatorProgressing,
159-
Status: configv1.ConditionFalse,
160-
Message: fmt.Sprintf("Done deploying %s.", olmversion.OLMVersion),
205+
Status: configv1.ConditionTrue,
206+
Message: fmt.Sprintf("Installing %s", olmversion.OLMVersion),
161207
LastTransitionTime: metav1.Now(),
162208
},
163209
configv1.ClusterOperatorStatusCondition{
164210
Type: configv1.OperatorFailing,
165211
Status: configv1.ConditionFalse,
166-
Message: fmt.Sprintf("Done deploying %s.", olmversion.OLMVersion),
167212
LastTransitionTime: metav1.Now(),
168213
},
169214
configv1.ClusterOperatorStatusCondition{
170215
Type: configv1.OperatorAvailable,
171-
Status: configv1.ConditionTrue,
172-
Message: fmt.Sprintf("Done deploying %s.", olmversion.OLMVersion),
216+
Status: configv1.ConditionFalse,
173217
LastTransitionTime: metav1.Now(),
174218
},
175219
},
176-
Versions: []configv1.OperandVersion{{
220+
},
221+
})
222+
if createErr != nil {
223+
log.Errorf("Failed to create cluster operator: %v\n", createErr)
224+
return
225+
}
226+
existing = created
227+
err = nil
228+
}
229+
if err != nil {
230+
log.Errorf("Unable to retrieve cluster operator: %v", err)
231+
return
232+
}
233+
234+
// update the status with the appropriate state
235+
previousStatus := existing.Status.DeepCopy()
236+
switch {
237+
case successfulSyncs > 0:
238+
setOperatorStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
239+
Type: configv1.OperatorFailing,
240+
Status: configv1.ConditionFalse,
241+
})
242+
setOperatorStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
243+
Type: configv1.OperatorProgressing,
244+
Status: configv1.ConditionFalse,
245+
Message: fmt.Sprintf("Deployed %s", olmversion.OLMVersion),
246+
})
247+
setOperatorStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
248+
Type: configv1.OperatorAvailable,
249+
Status: configv1.ConditionTrue,
250+
})
251+
// we set the versions array when all the latest code is deployed and running - in this case,
252+
// the sync method is responsible for guaranteeing that happens before it returns nil
253+
if version := os.Getenv("RELEASE_VERSION"); len(version) > 0 {
254+
existing.Status.Versions = []configv1.OperandVersion{
255+
{
177256
Name: "operator",
178-
Version: olmversion.Full(),
179-
}},
180-
}
181-
_, err = configClient.ClusterOperators().UpdateStatus(created)
182-
if err != nil {
183-
log.Fatalf("ClusterOperator update status failed: %v", err)
257+
Version: version,
258+
},
259+
{
260+
Name: "operator-lifecycle-manager",
261+
Version: olmversion.OLMVersion,
262+
},
184263
}
185-
} else if err != nil {
186-
log.Fatalf("ClusterOperators get failed: %v", err)
187264
} else {
188-
clusteroperatorv1helpers.SetStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
189-
Type: configv1.OperatorProgressing,
190-
Status: configv1.ConditionFalse,
191-
Message: fmt.Sprintf("Done deploying %s.", olmversion.OLMVersion),
192-
LastTransitionTime: metav1.Now(),
193-
})
194-
clusteroperatorv1helpers.SetStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
195-
Type: configv1.OperatorFailing,
196-
Status: configv1.ConditionFalse,
197-
Message: fmt.Sprintf("Done deploying %s.", olmversion.OLMVersion),
198-
LastTransitionTime: metav1.Now(),
199-
})
200-
clusteroperatorv1helpers.SetStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
201-
Type: configv1.OperatorAvailable,
202-
Status: configv1.ConditionTrue,
203-
Message: fmt.Sprintf("Done deploying %s.", olmversion.OLMVersion),
204-
LastTransitionTime: metav1.Now(),
205-
})
206-
207-
olmOperandVersion := configv1.OperandVersion{Name: "operator", Version: olmversion.Full()}
208-
// look for operator version, even though in OLM's case should only be one
209-
for _, item := range existing.Status.Versions {
210-
if item.Name == "operator" && item != olmOperandVersion {
211-
// if a cluster wide upgrade has occurred, hopefully any existing operator statuses have been deleted
212-
log.Infof("Updating version from %v to %v\n", item.Version, olmversion.Full())
213-
}
214-
}
215-
operatorv1helpers.SetOperandVersion(&existing.Status.Versions, olmOperandVersion)
216-
_, err = configClient.ClusterOperators().UpdateStatus(existing)
217-
if err != nil {
218-
log.Fatalf("ClusterOperator update status failed: %v", err)
219-
}
265+
existing.Status.Versions = nil
266+
}
267+
default:
268+
setOperatorStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
269+
Type: configv1.OperatorFailing,
270+
Status: configv1.ConditionTrue,
271+
Message: "Waiting for updates to take effect",
272+
})
273+
setOperatorStatusCondition(&existing.Status.Conditions, configv1.ClusterOperatorStatusCondition{
274+
Type: configv1.OperatorProgressing,
275+
Status: configv1.ConditionFalse,
276+
Message: fmt.Sprintf("Waiting to see update %s succeed", olmversion.OLMVersion),
277+
})
278+
// TODO: use % errors within a window to report available
279+
}
280+
281+
// update the status
282+
if !reflect.DeepEqual(previousStatus, &existing.Status) {
283+
if _, err := configClient.ClusterOperators().UpdateStatus(existing); err != nil {
284+
log.Errorf("Unable to update cluster operator status: %v", err)
220285
}
221286
}
287+
288+
// if we've reported success, we can sleep longer, otherwise we want to keep watching for
289+
// successful
290+
if successfulSyncs > 0 {
291+
time.Sleep(5 * time.Minute)
292+
}
293+
294+
}, 5*time.Second, stopCh)
295+
}
296+
297+
func setOperatorStatusCondition(conditions *[]configv1.ClusterOperatorStatusCondition, newCondition configv1.ClusterOperatorStatusCondition) {
298+
if conditions == nil {
299+
conditions = &[]configv1.ClusterOperatorStatusCondition{}
300+
}
301+
existingCondition := findOperatorStatusCondition(*conditions, newCondition.Type)
302+
if existingCondition == nil {
303+
newCondition.LastTransitionTime = metav1.NewTime(time.Now())
304+
*conditions = append(*conditions, newCondition)
305+
return
222306
}
223307

224-
<-done
308+
if existingCondition.Status != newCondition.Status {
309+
existingCondition.Status = newCondition.Status
310+
existingCondition.LastTransitionTime = newCondition.LastTransitionTime
311+
}
312+
313+
existingCondition.Reason = newCondition.Reason
314+
existingCondition.Message = newCondition.Message
315+
}
316+
317+
func findOperatorStatusCondition(conditions []configv1.ClusterOperatorStatusCondition, conditionType configv1.ClusterStatusConditionType) *configv1.ClusterOperatorStatusCondition {
318+
for i := range conditions {
319+
if conditions[i].Type == conditionType {
320+
return &conditions[i]
321+
}
322+
}
323+
324+
return nil
225325
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDGDCCAgCgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdsb2Nh
3+
bGhvc3QtY2FAMTU1MjQyMzAyMDAeFw0xOTAzMTIyMDM3MDFaFw0yMDAzMTEyMDM3
4+
MDFaMB8xHTAbBgNVBAMMFGxvY2FsaG9zdEAxNTUyNDIzMDIxMIIBIjANBgkqhkiG
5+
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAubkXRqN2xYxJiVhMjHnOtPCkU44QcLosVpIj
6+
tbUgzjJt0BDv/XNCMhbpD3dfKjMKZiKXt1dKDK2Tl52AceWqipVQlCf7kiX+CjuO
7+
gTAIEbVC7FWdu/sDI8BWbhs5knT+8Y7a5uGVexclZifvcbASuVtedLH47XI25Ak4
8+
s103Usy5Z2WXOLd79w/tsAr1kvQzveIdbn+upMu4to2wmfXhiLaU2qMhGoz+2hzm
9+
z+SXkB7uCgFbGuLIUj99/faSZ3CAH6EwPIerAKtY+1hdVmsjqpIrSs4jD7YyfmVN
10+
3+/MLTSMyHrghHYKt/SiRdCuVrbMhCylU8NFry+iuBIsOA202QIDAQABo1wwWjAO
11+
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
12+
ADAlBgNVHREEHjAcgglsb2NhbGhvc3SCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG
13+
9w0BAQsFAAOCAQEAacr9G8nNsHQpLCW+0meGmDz9deTfLYldFCbCjsPiUDWs9tUn
14+
O+04ykac2tEqZt2Ovkp6gntRPBCOKpgwHYvo0CJtCaL4yh6wYMvlbjHmHR/y+Ioy
15+
HymMmaQ06iVIhb2KoKFJvFtFUVNg6QE9w7dm9/C73eHcv3JhqYhGw3qBfUI6lmIc
16+
lWGj6WGVNfslofTYMkshbRGNZ3gFGkvcQvPOhKb/K4A3X9ZTGy9XyydVAOpdk/5n
17+
FBD4gOJJVSq2jJ5SOTJd5Z/YrY2tbCfZeuuPuxBK4XG3hnLN2fk9URwfCDc9EUQg
18+
aYagxskTB6jaDkFD5lfXxEc3W+/mP62i7mH/fQ==
19+
-----END CERTIFICATE-----
20+
-----BEGIN CERTIFICATE-----
21+
MIIC4jCCAcqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdsb2Nh
22+
bGhvc3QtY2FAMTU1MjQyMzAyMDAeFw0xOTAzMTIyMDM3MDBaFw0yMDAzMTEyMDM3
23+
MDBaMCIxIDAeBgNVBAMMF2xvY2FsaG9zdC1jYUAxNTUyNDIzMDIwMIIBIjANBgkq
24+
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnQb0E1iZ/R1J8bdzDP/EFx73JpU6fw6T
25+
aTY9QTWWgt4EcamLpJK5Z+dOLhj/i6rQbe/vKpI6BbBo+S6MuBemyUbc4VpoTde6
26+
Hn26uWSlkQA72GLHYWvD+ahdRpLxOFddog9xcfEoYN/rlpwMp030y6clQhrb4WML
27+
x1uQzqyOvzRHAN4NqxmLXbepTyWqiM3tLe2f4mPfcg/vhwQ5TSqR/Rm3FPh3rDdA
28+
zvk9bGkvyX8iAUoLw/0aHe2dzTfnvBvkTJFEaLq61FLQ/zfMVRhPI2Fwljxq+jSq
29+
FoYju/vr1sWxKc+AFxDdAZdRey2Afi1bVf8JHiDU8FSe9UcfqBUoyQIDAQABoyMw
30+
ITAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsF
31+
AAOCAQEAmrIS4kJNVjKj4vSj0lNWzOjk31CI26rKwPo+cFhvnPh+eg6wI+3I/gLC
32+
yf9X5KIPaNS5MGzNEmpr7Ml7IviqUn8rSoVryoQwKtqnMhsGr3/Y/Rrd27OIYEW+
33+
6/phRyI2rM8Vzo0RVdqcQT+6qvknbZ4fr/3Or3YbjycyfqNeL0SzXff+c8s9skDw
34+
r9OV5uMvmVJv3VNBhAEX83I4zJsfrH9XtAmz255aw24vBGMUHYEdH15K/IBxh4LZ
35+
Y5AXZhVazjlzwWwnUpu8k88vesCUay8c4VtXfXHQTk/oS/ZDn7eQ7hTvzqYfEH2k
36+
znJYRthnuUZo6M/rtMWzXK6QuunRtg==
37+
-----END CERTIFICATE-----
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEAubkXRqN2xYxJiVhMjHnOtPCkU44QcLosVpIjtbUgzjJt0BDv
3+
/XNCMhbpD3dfKjMKZiKXt1dKDK2Tl52AceWqipVQlCf7kiX+CjuOgTAIEbVC7FWd
4+
u/sDI8BWbhs5knT+8Y7a5uGVexclZifvcbASuVtedLH47XI25Ak4s103Usy5Z2WX
5+
OLd79w/tsAr1kvQzveIdbn+upMu4to2wmfXhiLaU2qMhGoz+2hzmz+SXkB7uCgFb
6+
GuLIUj99/faSZ3CAH6EwPIerAKtY+1hdVmsjqpIrSs4jD7YyfmVN3+/MLTSMyHrg
7+
hHYKt/SiRdCuVrbMhCylU8NFry+iuBIsOA202QIDAQABAoIBAEqc4o39c+TvdEea
8+
Ur6I3RNyLgJna5FuKgvpkDEbAH/2YImblF7VZD2tWJpfEbtpX/8iXKNKjTREs6vQ
9+
md6oLviX/hRXb8kKPGIuBRU/j65VjPpXdxQjRuKhDdgUVe/R0u6GvsjMzfnylZLR
10+
7m9VFmCjJXJqYaA7J3Q7hC0DAQvhBiWk0lZHR7cjGeG37fIT2yzH7gf8M4VeYjCn
11+
asatNUuAOORVfGudtKLCgFk/bmO1Nb5UwCYcz4OXVEpDBWrcg1SsvYwKxyUDxO8a
12+
8A7TAWWEXjWK+sPmaJkUzRfnd/1chvlzcaawXfgfXRHcAaLWRaBu4fdYS7fwMYy6
13+
+/0Pa5ECgYEA0GCkaAl7qicfHTY6xTkBvJwkXu/rDIfzJCRVtdlXOhPmJ+F3+0Rj
14+
0d+O6LMNSyJpYdOYeWOJbjHMJ92XIRJVxqF+K2O6dToEMTG2XbqMm2gtyn16BoTt
15+
ngzcWqeo+zqwvHxLcM6L/tjivnbsI7mVDpdcBJZwVd6VwrR2NgRh0tUCgYEA5CsF
16+
rJUlOR3JJ1CUTrT1G4smBES00lL3QFlhkiF4zWOW6NwhswZlYPkzqe6tgxmtGAuQ
17+
mJINMcqWUkU18BWLh8RRTH+oKcUbmZkTqP9k/bqe6foIm8UyxVsSF80S4tRtMcWm
18+
87Nd2h+FbYY2MP9RFscdDDd5FHf+weSCbnn0s/UCgYEAz05WQeqtTSp+meFJtsxw
19+
HeR5irnFbkIScvJzEueXEACcCTEW3LO9Wx6+XmND5mvly51nI90S7L4+Das2n4BO
20+
Nb6UdzZQWi/N2+NJOxZMrI+Ifts2eyXkAElrMAV85/QLwHkn1KKoRHIhortNUn1e
21+
/ZU3xpikScmX1I0UzciuScECgYAbWrEOdL8GrvR7uyRcn0M3byI6psYK5RlxZIXX
22+
EB48eXERL7r2jJDA5H92IwA4VG61EEXglLnyOzh0WonR47NbroSUqEVP5KqfaoO5
23+
4gyIgsQkhu5bRnQExxtPMS3Pdeo1al3On7Vjvh2v+MQscZ+WHH72BPyGILCxLCUa
24+
+5IDtQKBgGE1Wl2dmdAyedzCX93oOjnVQ2xdH4s+4k7yHBYEt9AIzbuZCSZLMsf+
25+
hDoU/TokDRXkrHnRvZvhpljgjRJULktnmZxRWW8e/YXrp+gTvSq7/bZCob8Dgs80
26+
w21YuIgo6sXV2uvqGUbZ3YvJQU0GnoFB/GztGlmuVyU0jpsJKq5P
27+
-----END RSA PRIVATE KEY-----

manifests/0000_50_olm_06-olm-operator.deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ spec:
3838
path: /healthz
3939
port: 8080
4040
env:
41+
- name: RELEASE_VERSION
42+
value: "0.0.1-snapshot"
4143
- name: OPERATOR_NAMESPACE
4244
valueFrom:
4345
fieldRef:

manifests/0000_50_olm_14-operatorstatus.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ apiVersion: config.openshift.io/v1
33
kind: ClusterOperator
44
metadata:
55
name: operator-lifecycle-manager
6+
status:
7+
versions:
8+
- name: operator
9+
version: "0.0.1-snapshot"

0 commit comments

Comments
 (0)