Skip to content

Commit fbaaeb4

Browse files
authoredApr 28, 2021
[release-4.7] Bug 1950926: Extend OLM data with CSV display name (#400) (#402)
1 parent 23a2989 commit fbaaeb4

File tree

7 files changed

+229
-80
lines changed

7 files changed

+229
-80
lines changed
 

‎docs/insights-archive-sample/config/olm_operators.json

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[
22
{
33
"name": "eap.openshift-operators",
4+
"displayName": "JBoss EAP",
45
"version": "v2.1.1",
56
"csv_conditions": [
67
{
@@ -42,6 +43,7 @@
4243
},
4344
{
4445
"name": "elasticsearch-operator.openshift-operators-redhat",
46+
"displayName": "OpenShift Elasticsearch Operator",
4547
"version": "4.6.0-202102200141.p0",
4648
"csv_conditions": [
4749
{
@@ -83,6 +85,7 @@
8385
},
8486
{
8587
"name": "kiali-ossm.openshift-operators",
88+
"displayName": "Kiali Operator",
8689
"version": "v1.24.5",
8790
"csv_conditions": [
8891
{
@@ -124,6 +127,7 @@
124127
},
125128
{
126129
"name": "postgresql-operator-dev4devs-com.psql-test",
130+
"displayName": "PostgreSQL Operator by Dev4Ddevs.com",
127131
"version": "v0.1.1",
128132
"csv_conditions": [
129133
{
@@ -165,6 +169,7 @@
165169
},
166170
{
167171
"name": "radanalytics-spark.openshift-operators",
172+
"displayName": "Apache Spark Operator",
168173
"version": "v1.1.0",
169174
"csv_conditions": [
170175
{

‎pkg/gather/clusterconfig/olm_operators.go

+78-39
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package clusterconfig
22

33
import (
44
"context"
5-
"encoding/json"
5+
"fmt"
66
"strings"
77

88
"k8s.io/apimachinery/pkg/api/errors"
99
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10-
"k8s.io/klog/v2"
1110

1211
"k8s.io/apimachinery/pkg/runtime/schema"
1312
"k8s.io/client-go/dynamic"
@@ -17,13 +16,17 @@ import (
1716

1817
var (
1918
operatorGVR = schema.GroupVersionResource{Group: "operators.coreos.com", Version: "v1", Resource: "operators"}
20-
clusterServiceVersionGVR = schema.GroupVersionResource{Group: "operators.coreos.com", Version: "v1alpha1", Resource: "clusterserviceversions"}
19+
clusterServiceVersionGVR = schema.GroupVersionResource{
20+
Group: "operators.coreos.com",
21+
Version: "v1alpha1",
22+
Resource: "clusterserviceversions"}
2123
)
2224

2325
type olmOperator struct {
24-
Name string `json:"name"`
25-
Version string `json:"version"`
26-
Conditions []interface{} `json:"csv_conditions"`
26+
Name string `json:"name"`
27+
DisplayName string `json:"displayName"`
28+
Version string `json:"version"`
29+
Conditions []interface{} `json:"csv_conditions"`
2730
}
2831

2932
// ClusterServiceVersion helper struct
@@ -63,39 +66,56 @@ func gatherOLMOperators(ctx context.Context, dynamicClient dynamic.Interface) ([
6366
}
6467
var refs []interface{}
6568
olms := []olmOperator{}
69+
errs := []error{}
6670
for _, i := range olmOperators.Items {
71+
newOlm := olmOperator{
72+
Name: i.GetName(),
73+
}
6774
err := parseJSONQuery(i.Object, "status.components.refs", &refs)
6875
if err != nil {
69-
klog.Errorf("Cannot find \"status.components.refs\" in %s definition: %v", i.GetName(), err)
76+
// if no references are found then add an error and OLM operator with only name and continue
77+
errs = append(errs, fmt.Errorf("cannot find \"status.components.refs\" in %s definition: %v", i.GetName(), err))
78+
olms = append(olms, newOlm)
7079
continue
7180
}
7281
for _, r := range refs {
73-
csvRef := getCSVRefFromRefs(r)
82+
csvRef, err := findCSVRefInRefs(r)
83+
if err != nil {
84+
errs = append(errs, err)
85+
olms = append(olms, newOlm)
86+
continue
87+
}
88+
// CSV reference can still be nil
7489
if csvRef == nil {
7590
continue
7691
}
77-
conditions, err := getCSVConditions(ctx, dynamicClient, csvRef)
92+
newOlm.Version = csvRef.Version
93+
94+
name, conditions, err := getCSVAndParse(ctx, dynamicClient, csvRef)
7895
if err != nil {
79-
klog.Errorf("failed to get %s conditions: %v", csvRef.Name, err)
96+
// append the error and the OLM data we already have and continue
97+
errs = append(errs, err)
98+
olms = append(olms, newOlm)
8099
continue
81100
}
82-
olmO := olmOperator{
83-
Name: i.GetName(),
84-
Version: csvRef.Version,
85-
Conditions: conditions,
86-
}
87-
if isInArray(olmO, olms) {
101+
newOlm.DisplayName = name
102+
newOlm.Conditions = conditions
103+
104+
if isInArray(newOlm, olms) {
88105
continue
89106
}
90-
olms = append(olms, olmO)
107+
olms = append(olms, newOlm)
91108
}
92109
}
93110
if len(olms) == 0 {
94111
return nil, nil
95112
}
96113
r := record.Record{
97114
Name: "config/olm_operators",
98-
Item: OlmOperatorAnonymizer{operators: olms},
115+
Item: record.JSONMarshaller{Object: olms},
116+
}
117+
if len(errs) != 0 {
118+
return []record.Record{r}, errs
99119
}
100120
return []record.Record{r}, nil
101121
}
@@ -109,48 +129,67 @@ func isInArray(o olmOperator, a []olmOperator) bool {
109129
return false
110130
}
111131

112-
func getCSVRefFromRefs(r interface{}) *csvRef {
132+
//getCSVAndParse gets full CSV definition from csvRef and tries to parse the definition
133+
func getCSVAndParse(ctx context.Context, dynamicClient dynamic.Interface, csvRef *csvRef) (name string, conditions []interface{}, err error) {
134+
csv, err := getCsvFromRef(ctx, dynamicClient, csvRef)
135+
if err != nil {
136+
return "", nil, fmt.Errorf("failed to get %s ClusterServiceVersion: %v", csvRef.Name, err)
137+
}
138+
name, conditions, err = parseCsv(csv)
139+
140+
if err != nil {
141+
return "", nil, fmt.Errorf("cannot read %s ClusterServiceVersion attributes: %v", csvRef.Name, err)
142+
}
143+
144+
return name, conditions, nil
145+
}
146+
147+
//findCSVRefInRefs tries to find ClusterServiceVersion reference in the references
148+
//and parse the ClusterServiceVersion if successful.
149+
//It can return nil with no error if the CSV was not found
150+
func findCSVRefInRefs(r interface{}) (*csvRef, error) {
113151
refMap, ok := r.(map[string]interface{})
114152
if !ok {
115-
klog.Errorf("Cannot convert %s to map[string]interface{}", r)
116-
return nil
153+
return nil, fmt.Errorf("cannot convert %s to map[string]interface{}", r)
117154
}
118155
// version is part of the name of ClusterServiceVersion
119156
if refMap["kind"] == "ClusterServiceVersion" {
120157
name := refMap["name"].(string)
158+
if !strings.Contains(name, ".") {
159+
return nil, fmt.Errorf("clusterserviceversion \"%s\" probably doesn't include version", name)
160+
}
121161
nameVer := strings.SplitN(name, ".", 2)
122162
csvRef := &csvRef{
123163
Name: name,
124164
Namespace: refMap["namespace"].(string),
125165
Version: nameVer[1],
126166
}
127-
return csvRef
167+
return csvRef, nil
128168
}
129-
return nil
169+
return nil, nil
130170
}
131171

132-
func getCSVConditions(ctx context.Context, dynamicClient dynamic.Interface, csvRef *csvRef) ([]interface{}, error) {
172+
func getCsvFromRef(ctx context.Context, dynamicClient dynamic.Interface, csvRef *csvRef) (map[string]interface{}, error) {
133173
csv, err := dynamicClient.Resource(clusterServiceVersionGVR).Namespace(csvRef.Namespace).Get(ctx, csvRef.Name, metav1.GetOptions{})
134174
if err != nil {
135175
return nil, err
136176
}
177+
return csv.Object, nil
178+
}
179+
180+
//parseCsv tries to parse "status.conditions" and "spec.displayName" from the input map.
181+
// Returns an error if any of the values cannot be parsed.
182+
func parseCsv(csv map[string]interface{}) (string, []interface{}, error) {
137183
var conditions []interface{}
138-
err = parseJSONQuery(csv.Object, "status.conditions", &conditions)
184+
var name string
185+
err := parseJSONQuery(csv, "status.conditions", &conditions)
139186
if err != nil {
140-
return nil, err
187+
return "", nil, err
188+
}
189+
err = parseJSONQuery(csv, "spec.displayName", &name)
190+
if err != nil {
191+
return "", nil, err
141192
}
142-
return conditions, nil
143-
}
144-
145-
// OlmOperatorAnonymizer implements HostSubnet serialization
146-
type OlmOperatorAnonymizer struct{ operators []olmOperator }
147-
148-
// Marshal implements OlmOperator serialization
149-
func (a OlmOperatorAnonymizer) Marshal(_ context.Context) ([]byte, error) {
150-
return json.Marshal(a.operators)
151-
}
152193

153-
// GetExtension returns extension for OlmOperator object
154-
func (a OlmOperatorAnonymizer) GetExtension() string {
155-
return "json"
194+
return name, conditions, nil
156195
}

‎pkg/gather/clusterconfig/olm_operators_test.go

+109-41
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package clusterconfig
22

33
import (
44
"context"
5+
"fmt"
56
"io/ioutil"
67
"os"
8+
"reflect"
79
"testing"
810

11+
"github.com/openshift/insights-operator/pkg/record"
912
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1013
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1114
"k8s.io/apimachinery/pkg/runtime"
@@ -14,50 +17,115 @@ import (
1417
dynamicfake "k8s.io/client-go/dynamic/fake"
1518
)
1619

17-
func TestOLMOperatorsGather(t *testing.T) {
18-
olmOpContent, err := readFromFile("testdata/olm_operator_1.yaml")
19-
if err != nil {
20-
t.Fatal("test failed to read OLM operator data", err)
20+
func Test_OLMOperators_Gather(t *testing.T) {
21+
var cases = []struct {
22+
testName string
23+
olmOperatorFileName string
24+
csvFileName string
25+
expectedError error
26+
expecteOlmOperator olmOperator
27+
}{
28+
{
29+
"All OLM operator data is available",
30+
"testdata/olm_operator_1.yaml",
31+
"testdata/csv_1.yaml",
32+
nil,
33+
olmOperator{
34+
Name: "test-olm-operator",
35+
DisplayName: "Testing operator",
36+
Version: "v1.2.3",
37+
Conditions: []interface{}{
38+
map[string]interface{}{
39+
"lastTransitionTime": "2021-03-02T08:52:24Z",
40+
"lastUpdateTime": "2021-03-02T08:52:24Z",
41+
"message": "requirements not yet checked",
42+
"phase": "Pending",
43+
"reason": "RequirementsUnknown",
44+
},
45+
map[string]interface{}{
46+
"lastTransitionTime": "2021-03-02T08:52:24Z",
47+
"lastUpdateTime": "2021-03-02T08:52:24Z",
48+
"message": "all requirements found, attempting install",
49+
"phase": "InstallReady",
50+
"reason": "AllRequirementsMet",
51+
},
52+
},
53+
},
54+
},
55+
{
56+
"Operator doesn't have CSV reference",
57+
"testdata/olm_operator_2.yaml",
58+
"testdata/csv_1.yaml",
59+
fmt.Errorf("cannot find \"status.components.refs\" in test-olm-operator-with-no-ref definition: key refs wasn't found in map[] "),
60+
olmOperator{
61+
Name: "test-olm-operator-with-no-ref",
62+
},
63+
},
64+
{
65+
"Operator CSV doesn't have the displayName",
66+
"testdata/olm_operator_1.yaml",
67+
"testdata/csv_2.yaml",
68+
fmt.Errorf("cannot read test-olm-operator.v1.2.3 ClusterServiceVersion attributes: key displayName wasn't found in map[] "),
69+
olmOperator{
70+
Name: "test-olm-operator",
71+
Version: "v1.2.3",
72+
},
73+
},
74+
{
75+
"Operator with unrecognizable CSV version",
76+
"testdata/olm_operator_3.yaml",
77+
"testdata/csv_1.yaml",
78+
fmt.Errorf("clusterserviceversion \"name-without-version\" probably doesn't include version"),
79+
olmOperator{
80+
Name: "test-olm-operator-no-version",
81+
},
82+
},
2183
}
2284

23-
csvContent, err := readFromFile("testdata/csv_1.yaml")
24-
if err != nil {
25-
t.Fatal("test failed to read CSV ", err)
26-
}
27-
client := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), map[schema.GroupVersionResource]string{
28-
operatorGVR: "OperatorsList",
29-
clusterServiceVersionGVR: "ClusterServiceVersionsList",
30-
})
31-
err = createUnstructuredResource(olmOpContent, client, operatorGVR)
32-
if err != nil {
33-
t.Fatal("cannot create OLM operator ", err)
34-
}
35-
err = createUnstructuredResource(csvContent, client, clusterServiceVersionGVR)
36-
if err != nil {
37-
t.Fatal("cannot create ClusterServiceVersion ", err)
38-
}
85+
for _, tt := range cases {
86+
tt := tt
87+
t.Run(tt.testName, func(t *testing.T) {
88+
olmOpContent, err := readFromFile(tt.olmOperatorFileName)
89+
if err != nil {
90+
t.Fatal("test failed to read OLM operator data", err)
91+
}
3992

40-
ctx := context.Background()
41-
records, errs := gatherOLMOperators(ctx, client)
42-
if len(errs) > 0 {
43-
t.Errorf("unexpected errors: %#v", errs)
44-
return
45-
}
46-
if len(records) != 1 {
47-
t.Fatalf("unexpected number or records %d", len(records))
48-
}
49-
ooa, ok := records[0].Item.(OlmOperatorAnonymizer)
50-
if !ok {
51-
t.Fatalf("returned item is not of type OlmOperatorAnonymizer")
52-
}
53-
if ooa.operators[0].Name != "test-olm-operator" {
54-
t.Fatalf("unexpected name of gathered OLM operator %s", ooa.operators[0].Name)
55-
}
56-
if ooa.operators[0].Version != "v1.2.3" {
57-
t.Fatalf("unexpected version of gathered OLM operator %s", ooa.operators[0].Version)
58-
}
59-
if len(ooa.operators[0].Conditions) != 2 {
60-
t.Fatalf("unexpected number of conditions %s", ooa.operators[0].Conditions...)
93+
csvContent, err := readFromFile(tt.csvFileName)
94+
if err != nil {
95+
t.Fatal("test failed to read CSV ", err)
96+
}
97+
client := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), map[schema.GroupVersionResource]string{
98+
operatorGVR: "OperatorsList",
99+
clusterServiceVersionGVR: "ClusterServiceVersionsList",
100+
})
101+
err = createUnstructuredResource(olmOpContent, client, operatorGVR)
102+
if err != nil {
103+
t.Fatal("cannot create OLM operator ", err)
104+
}
105+
err = createUnstructuredResource(csvContent, client, clusterServiceVersionGVR)
106+
if err != nil {
107+
t.Fatal("cannot create ClusterServiceVersion ", err)
108+
}
109+
110+
ctx := context.Background()
111+
records, errs := gatherOLMOperators(ctx, client)
112+
if len(errs) > 0 {
113+
if errs[0].Error() != tt.expectedError.Error() {
114+
t.Fatalf("unexpected errors: %v", errs[0].Error())
115+
}
116+
}
117+
if len(records) != 1 {
118+
t.Fatalf("unexpected number or records %d", len(records))
119+
}
120+
ooa, ok := records[0].Item.(record.JSONMarshaller).Object.([]olmOperator)
121+
if !ok {
122+
t.Fatalf("returned item is not of type []olmOperator")
123+
}
124+
sameOp := reflect.DeepEqual(ooa[0], tt.expecteOlmOperator)
125+
if !sameOp {
126+
t.Fatalf("Gathered %s operator is not equal to expected %s ", ooa[0], tt.expecteOlmOperator)
127+
}
128+
})
61129
}
62130
}
63131

‎pkg/gather/clusterconfig/testdata/csv_1.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ kind: ClusterServiceVersion
33
metadata:
44
name: test-olm-operator.v1.2.3
55
namespace: test-olm-operator
6+
spec:
7+
displayName: "Testing operator"
68
status:
79
conditions:
810
- lastTransitionTime: "2021-03-02T08:52:24Z"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: operators.coreos.com/v1alpha1
2+
kind: ClusterServiceVersion
3+
metadata:
4+
name: test-olm-operator.v1.2.3
5+
namespace: test-olm-operator
6+
spec: {}
7+
status:
8+
conditions:
9+
- lastTransitionTime: "2021-03-02T08:52:24Z"
10+
lastUpdateTime: "2021-03-02T08:52:24Z"
11+
message: requirements not yet checked
12+
phase: Pending
13+
reason: RequirementsUnknown
14+
- lastTransitionTime: "2021-03-02T08:52:24Z"
15+
lastUpdateTime: "2021-03-02T08:52:24Z"
16+
message: all requirements found, attempting install
17+
phase: InstallReady
18+
reason: AllRequirementsMet
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: operators.coreos.com/v1
2+
kind: Operator
3+
metadata:
4+
name: test-olm-operator-with-no-ref
5+
status:
6+
components: {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: operators.coreos.com/v1
2+
kind: Operator
3+
metadata:
4+
name: test-olm-operator-no-version
5+
status:
6+
components:
7+
refs:
8+
- apiVersion: operators.coreos.com/v1alpha1
9+
kind: ClusterServiceVersion
10+
name: name-without-version
11+
namespace: test-olm-operator

0 commit comments

Comments
 (0)
Please sign in to comment.