Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b0a17be

Browse files
author
Per Goncalves da Silva
committedFeb 11, 2025
Decompose RegistryV1ToHelmChart function
Signed-off-by: Per Goncalves da Silva <pegoncal@redhat.com>
1 parent c0a2964 commit b0a17be

File tree

2 files changed

+77
-95
lines changed

2 files changed

+77
-95
lines changed
 

Diff for: ‎internal/rukpak/convert/registryv1.go

+33-28
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,44 @@ type Plain struct {
4242
}
4343

4444
func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace string, watchNamespaces []string) (*chart.Chart, error) {
45+
reg, err := ParseFS(ctx, rv1)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
plain, err := Convert(reg, installNamespace, watchNamespaces)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
chrt := &chart.Chart{Metadata: &chart.Metadata{}}
56+
chrt.Metadata.Annotations = reg.CSV.GetAnnotations()
57+
for _, obj := range plain.Objects {
58+
jsonData, err := json.Marshal(obj)
59+
if err != nil {
60+
return nil, err
61+
}
62+
hash := sha256.Sum256(jsonData)
63+
chrt.Templates = append(chrt.Templates, &chart.File{
64+
Name: fmt.Sprintf("object-%x.json", hash[0:8]),
65+
Data: jsonData,
66+
})
67+
}
68+
69+
return chrt, nil
70+
}
71+
72+
func ParseFS(ctx context.Context, rv1 fs.FS) (RegistryV1, error) {
4573
l := log.FromContext(ctx)
4674

4775
reg := RegistryV1{}
4876
annotationsFileData, err := fs.ReadFile(rv1, filepath.Join("metadata", "annotations.yaml"))
4977
if err != nil {
50-
return nil, err
78+
return reg, err
5179
}
5280
annotationsFile := registry.AnnotationsFile{}
5381
if err := yaml.Unmarshal(annotationsFileData, &annotationsFile); err != nil {
54-
return nil, err
82+
return reg, err
5583
}
5684
reg.PackageName = annotationsFile.Annotations.PackageName
5785

@@ -100,14 +128,14 @@ func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace stri
100128
}
101129
return nil
102130
}); err != nil {
103-
return nil, err
131+
return reg, err
104132
}
105133

106134
if err := copyMetadataPropertiesToCSV(&reg.CSV, rv1); err != nil {
107-
return nil, err
135+
return reg, err
108136
}
109137

110-
return toChart(reg, installNamespace, watchNamespaces)
138+
return reg, nil
111139
}
112140

113141
// copyMetadataPropertiesToCSV copies properties from `metadata/propeties.yaml` (in the filesystem fsys) into
@@ -158,29 +186,6 @@ func copyMetadataPropertiesToCSV(csv *v1alpha1.ClusterServiceVersion, fsys fs.FS
158186
return nil
159187
}
160188

161-
func toChart(in RegistryV1, installNamespace string, watchNamespaces []string) (*chart.Chart, error) {
162-
plain, err := Convert(in, installNamespace, watchNamespaces)
163-
if err != nil {
164-
return nil, err
165-
}
166-
167-
chrt := &chart.Chart{Metadata: &chart.Metadata{}}
168-
chrt.Metadata.Annotations = in.CSV.GetAnnotations()
169-
for _, obj := range plain.Objects {
170-
jsonData, err := json.Marshal(obj)
171-
if err != nil {
172-
return nil, err
173-
}
174-
hash := sha256.Sum256(jsonData)
175-
chrt.Templates = append(chrt.Templates, &chart.File{
176-
Name: fmt.Sprintf("object-%x.json", hash[0:8]),
177-
Data: jsonData,
178-
})
179-
}
180-
181-
return chrt, nil
182-
}
183-
184189
func validateTargetNamespaces(supportedInstallModes sets.Set[string], installNamespace string, targetNamespaces []string) error {
185190
set := sets.New[string](targetNamespaces...)
186191
switch {

Diff for: ‎internal/rukpak/convert/registryv1_test.go

+44-67
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
package convert
1+
package convert_test
22

33
import (
44
"context"
55
"fmt"
6+
"github.com/operator-framework/operator-controller/internal/rukpak/convert"
67
"os"
78
"strings"
89
"testing"
@@ -49,22 +50,22 @@ func getCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) {
4950
func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) {
5051
var targetNamespaces []string
5152

52-
t.Log("RegistryV1 Suite Convert")
53+
t.Log("convert.RegistryV1 Suite Convert")
5354
t.Log("It should set the namespaces of the object correctly")
5455
t.Log("It should set the namespace to installnamespace if not available")
5556

5657
t.Log("By creating a registry v1 bundle")
5758
csv, svc := getCsvAndService()
5859

5960
unstructuredSvc := convertToUnstructured(t, svc)
60-
registryv1Bundle := RegistryV1{
61+
registryv1Bundle := convert.RegistryV1{
6162
PackageName: "testPkg",
6263
CSV: csv,
6364
Others: []unstructured.Unstructured{unstructuredSvc},
6465
}
6566

6667
t.Log("By converting to plain")
67-
plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces)
68+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces)
6869
require.NoError(t, err)
6970

7071
t.Log("By verifying if plain bundle has required objects")
@@ -80,7 +81,7 @@ func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) {
8081
func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) {
8182
var targetNamespaces []string
8283

83-
t.Log("RegistryV1 Suite Convert")
84+
t.Log("convert.RegistryV1 Suite Convert")
8485
t.Log("It should set the namespaces of the object correctly")
8586
t.Log("It should override namespace if already available")
8687

@@ -91,14 +92,14 @@ func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) {
9192
unstructuredSvc := convertToUnstructured(t, svc)
9293
unstructuredSvc.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"})
9394

94-
registryv1Bundle := RegistryV1{
95+
registryv1Bundle := convert.RegistryV1{
9596
PackageName: "testPkg",
9697
CSV: csv,
9798
Others: []unstructured.Unstructured{unstructuredSvc},
9899
}
99100

100101
t.Log("By converting to plain")
101-
plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces)
102+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces)
102103
require.NoError(t, err)
103104

104105
t.Log("By verifying if plain bundle has required objects")
@@ -114,7 +115,7 @@ func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) {
114115
func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) {
115116
var targetNamespaces []string
116117

117-
t.Log("RegistryV1 Suite Convert")
118+
t.Log("convert.RegistryV1 Suite Convert")
118119
t.Log("It should set the namespaces of the object correctly")
119120
t.Log("It should error when object is not supported")
120121
t.Log("It should error when unsupported GVK is passed")
@@ -132,14 +133,14 @@ func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) {
132133
unstructuredEvt := convertToUnstructured(t, event)
133134
unstructuredEvt.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Event"})
134135

135-
registryv1Bundle := RegistryV1{
136+
registryv1Bundle := convert.RegistryV1{
136137
PackageName: "testPkg",
137138
CSV: csv,
138139
Others: []unstructured.Unstructured{unstructuredEvt},
139140
}
140141

141142
t.Log("By converting to plain")
142-
plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces)
143+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces)
143144
require.Error(t, err)
144145
require.ErrorContains(t, err, "bundle contains unsupported resource")
145146
require.Nil(t, plainBundle)
@@ -148,7 +149,7 @@ func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) {
148149
func TestRegistryV1SuiteNamespaceClusterScoped(t *testing.T) {
149150
var targetNamespaces []string
150151

151-
t.Log("RegistryV1 Suite Convert")
152+
t.Log("convert.RegistryV1 Suite Convert")
152153
t.Log("It should set the namespaces of the object correctly")
153154
t.Log("It should not set ns cluster scoped object is passed")
154155
t.Log("It should not error when cluster scoped obj is passed and not set its namespace")
@@ -166,14 +167,14 @@ func TestRegistryV1SuiteNamespaceClusterScoped(t *testing.T) {
166167
unstructuredpriorityclass := convertToUnstructured(t, pc)
167168
unstructuredpriorityclass.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PriorityClass"})
168169

169-
registryv1Bundle := RegistryV1{
170+
registryv1Bundle := convert.RegistryV1{
170171
PackageName: "testPkg",
171172
CSV: csv,
172173
Others: []unstructured.Unstructured{unstructuredpriorityclass},
173174
}
174175

175176
t.Log("By converting to plain")
176-
plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces)
177+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces)
177178
require.NoError(t, err)
178179

179180
t.Log("By verifying if plain bundle has required objects")
@@ -242,7 +243,7 @@ func getBaseCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) {
242243
}
243244

244245
func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) {
245-
t.Log("RegistryV1 Suite Convert")
246+
t.Log("convert.RegistryV1 Suite Convert")
246247
t.Log("It should generate objects successfully based on target namespaces")
247248

248249
t.Log("It should convert into plain manifests successfully with AllNamespaces")
@@ -253,14 +254,14 @@ func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) {
253254
t.Log("By creating a registry v1 bundle")
254255
watchNamespaces := []string{""}
255256
unstructuredSvc := convertToUnstructured(t, svc)
256-
registryv1Bundle := RegistryV1{
257+
registryv1Bundle := convert.RegistryV1{
257258
PackageName: "testPkg",
258259
CSV: *csv,
259260
Others: []unstructured.Unstructured{unstructuredSvc},
260261
}
261262

262263
t.Log("By converting to plain")
263-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
264+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
264265
require.NoError(t, err)
265266

266267
t.Log("By verifying if plain bundle has required objects")
@@ -275,7 +276,7 @@ func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) {
275276
}
276277

277278
func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) {
278-
t.Log("RegistryV1 Suite Convert")
279+
t.Log("convert.RegistryV1 Suite Convert")
279280
t.Log("It should generate objects successfully based on target namespaces")
280281

281282
t.Log("It should convert into plain manifests successfully with MultiNamespace")
@@ -286,14 +287,14 @@ func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) {
286287
t.Log("By creating a registry v1 bundle")
287288
watchNamespaces := []string{"testWatchNs1", "testWatchNs2"}
288289
unstructuredSvc := convertToUnstructured(t, svc)
289-
registryv1Bundle := RegistryV1{
290+
registryv1Bundle := convert.RegistryV1{
290291
PackageName: "testPkg",
291292
CSV: *csv,
292293
Others: []unstructured.Unstructured{unstructuredSvc},
293294
}
294295

295296
t.Log("By converting to plain")
296-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
297+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
297298
require.NoError(t, err)
298299

299300
t.Log("By verifying if plain bundle has required objects")
@@ -308,7 +309,7 @@ func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) {
308309
}
309310

310311
func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) {
311-
t.Log("RegistryV1 Suite Convert")
312+
t.Log("convert.RegistryV1 Suite Convert")
312313
t.Log("It should generate objects successfully based on target namespaces")
313314

314315
t.Log("It should convert into plain manifests successfully with SingleNamespace")
@@ -319,14 +320,14 @@ func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) {
319320
t.Log("By creating a registry v1 bundle")
320321
watchNamespaces := []string{"testWatchNs1"}
321322
unstructuredSvc := convertToUnstructured(t, svc)
322-
registryv1Bundle := RegistryV1{
323+
registryv1Bundle := convert.RegistryV1{
323324
PackageName: "testPkg",
324325
CSV: *csv,
325326
Others: []unstructured.Unstructured{unstructuredSvc},
326327
}
327328

328329
t.Log("By converting to plain")
329-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
330+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
330331
require.NoError(t, err)
331332

332333
t.Log("By verifying if plain bundle has required objects")
@@ -341,7 +342,7 @@ func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) {
341342
}
342343

343344
func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) {
344-
t.Log("RegistryV1 Suite Convert")
345+
t.Log("convert.RegistryV1 Suite Convert")
345346
t.Log("It should generate objects successfully based on target namespaces")
346347

347348
t.Log("It should convert into plain manifests successfully with own namespace")
@@ -352,14 +353,14 @@ func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) {
352353
t.Log("By creating a registry v1 bundle")
353354
watchNamespaces := []string{installNamespace}
354355
unstructuredSvc := convertToUnstructured(t, svc)
355-
registryv1Bundle := RegistryV1{
356+
registryv1Bundle := convert.RegistryV1{
356357
PackageName: "testPkg",
357358
CSV: *csv,
358359
Others: []unstructured.Unstructured{unstructuredSvc},
359360
}
360361

361362
t.Log("By converting to plain")
362-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
363+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
363364
require.NoError(t, err)
364365

365366
t.Log("By verifying if plain bundle has required objects")
@@ -374,7 +375,7 @@ func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) {
374375
}
375376

376377
func TestRegistryV1SuiteGenerateErrorMultiNamespaceEmpty(t *testing.T) {
377-
t.Log("RegistryV1 Suite Convert")
378+
t.Log("convert.RegistryV1 Suite Convert")
378379
t.Log("It should generate objects successfully based on target namespaces")
379380

380381
t.Log("It should error when multinamespace mode is supported with an empty string in target namespaces")
@@ -385,20 +386,20 @@ func TestRegistryV1SuiteGenerateErrorMultiNamespaceEmpty(t *testing.T) {
385386
t.Log("By creating a registry v1 bundle")
386387
watchNamespaces := []string{"testWatchNs1", ""}
387388
unstructuredSvc := convertToUnstructured(t, svc)
388-
registryv1Bundle := RegistryV1{
389+
registryv1Bundle := convert.RegistryV1{
389390
PackageName: "testPkg",
390391
CSV: *csv,
391392
Others: []unstructured.Unstructured{unstructuredSvc},
392393
}
393394

394395
t.Log("By converting to plain")
395-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
396+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
396397
require.Error(t, err)
397398
require.Nil(t, plainBundle)
398399
}
399400

400401
func TestRegistryV1SuiteGenerateErrorSingleNamespaceDisabled(t *testing.T) {
401-
t.Log("RegistryV1 Suite Convert")
402+
t.Log("convert.RegistryV1 Suite Convert")
402403
t.Log("It should generate objects successfully based on target namespaces")
403404

404405
t.Log("It should error when single namespace mode is disabled with more than one target namespaces")
@@ -409,20 +410,20 @@ func TestRegistryV1SuiteGenerateErrorSingleNamespaceDisabled(t *testing.T) {
409410
t.Log("By creating a registry v1 bundle")
410411
watchNamespaces := []string{"testWatchNs1", "testWatchNs2"}
411412
unstructuredSvc := convertToUnstructured(t, svc)
412-
registryv1Bundle := RegistryV1{
413+
registryv1Bundle := convert.RegistryV1{
413414
PackageName: "testPkg",
414415
CSV: *csv,
415416
Others: []unstructured.Unstructured{unstructuredSvc},
416417
}
417418

418419
t.Log("By converting to plain")
419-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
420+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
420421
require.Error(t, err)
421422
require.Nil(t, plainBundle)
422423
}
423424

424425
func TestRegistryV1SuiteGenerateErrorAllNamespaceDisabled(t *testing.T) {
425-
t.Log("RegistryV1 Suite Convert")
426+
t.Log("convert.RegistryV1 Suite Convert")
426427
t.Log("It should generate objects successfully based on target namespaces")
427428

428429
t.Log("It should error when all namespace mode is disabled with target namespace containing an empty string")
@@ -438,50 +439,26 @@ func TestRegistryV1SuiteGenerateErrorAllNamespaceDisabled(t *testing.T) {
438439
t.Log("By creating a registry v1 bundle")
439440
watchNamespaces := []string{""}
440441
unstructuredSvc := convertToUnstructured(t, svc)
441-
registryv1Bundle := RegistryV1{
442+
registryv1Bundle := convert.RegistryV1{
442443
PackageName: "testPkg",
443444
CSV: *csv,
444445
Others: []unstructured.Unstructured{unstructuredSvc},
445446
}
446447

447448
t.Log("By converting to plain")
448-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
449+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
449450
require.Error(t, err)
450451
require.Nil(t, plainBundle)
451452
}
452453

453-
func TestRegistryV1SuiteGeneratePropagateCsvAnnotations(t *testing.T) {
454-
t.Log("RegistryV1 Suite Convert")
455-
t.Log("It should generate objects successfully based on target namespaces")
456-
457-
t.Log("It should propagate csv annotations to chart metadata annotation")
458-
baseCSV, svc := getBaseCsvAndService()
459-
csv := baseCSV.DeepCopy()
460-
csv.Spec.InstallModes = []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeMultiNamespace, Supported: true}}
461-
462-
t.Log("By creating a registry v1 bundle")
463-
watchNamespaces := []string{"testWatchNs1", "testWatchNs2"}
464-
unstructuredSvc := convertToUnstructured(t, svc)
465-
registryv1Bundle := RegistryV1{
466-
PackageName: "testPkg",
467-
CSV: *csv,
468-
Others: []unstructured.Unstructured{unstructuredSvc},
469-
}
470-
471-
t.Log("By converting to helm")
472-
chrt, err := toChart(registryv1Bundle, installNamespace, watchNamespaces)
473-
require.NoError(t, err)
474-
require.Contains(t, chrt.Metadata.Annotations, olmProperties)
475-
}
476-
477454
func TestRegistryV1SuiteReadBundleFileSystem(t *testing.T) {
478-
t.Log("RegistryV1 Suite Convert")
455+
t.Log("convert.RegistryV1 Suite Convert")
479456
t.Log("It should generate objects successfully based on target namespaces")
480457

481458
t.Log("It should read the registry+v1 bundle filesystem correctly")
482459
t.Log("It should include metadata/properties.yaml and csv.metadata.annotations['olm.properties'] in chart metadata")
483460
fsys := os.DirFS("testdata/combine-properties-bundle")
484-
chrt, err := RegistryV1ToHelmChart(context.Background(), fsys, "", nil)
461+
chrt, err := convert.RegistryV1ToHelmChart(context.Background(), fsys, "", nil)
485462
require.NoError(t, err)
486463
require.NotNil(t, chrt)
487464
require.NotNil(t, chrt.Metadata)
@@ -490,7 +467,7 @@ func TestRegistryV1SuiteReadBundleFileSystem(t *testing.T) {
490467
}
491468

492469
func TestRegistryV1SuiteGenerateNoWebhooks(t *testing.T) {
493-
t.Log("RegistryV1 Suite Convert")
470+
t.Log("convert.RegistryV1 Suite Convert")
494471
t.Log("It should generate objects successfully based on target namespaces")
495472

496473
t.Log("It should enforce limitations")
@@ -506,20 +483,20 @@ func TestRegistryV1SuiteGenerateNoWebhooks(t *testing.T) {
506483
},
507484
}
508485
watchNamespaces := []string{metav1.NamespaceAll}
509-
registryv1Bundle := RegistryV1{
486+
registryv1Bundle := convert.RegistryV1{
510487
PackageName: "testPkg",
511488
CSV: csv,
512489
}
513490

514491
t.Log("By converting to plain")
515-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
492+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
516493
require.Error(t, err)
517494
require.ErrorContains(t, err, "webhookDefinitions are not supported")
518495
require.Nil(t, plainBundle)
519496
}
520497

521498
func TestRegistryV1SuiteGenerateNoAPISerciceDefinitions(t *testing.T) {
522-
t.Log("RegistryV1 Suite Convert")
499+
t.Log("convert.RegistryV1 Suite Convert")
523500
t.Log("It should generate objects successfully based on target namespaces")
524501

525502
t.Log("It should enforce limitations")
@@ -537,13 +514,13 @@ func TestRegistryV1SuiteGenerateNoAPISerciceDefinitions(t *testing.T) {
537514
},
538515
}
539516
watchNamespaces := []string{metav1.NamespaceAll}
540-
registryv1Bundle := RegistryV1{
517+
registryv1Bundle := convert.RegistryV1{
541518
PackageName: "testPkg",
542519
CSV: csv,
543520
}
544521

545522
t.Log("By converting to plain")
546-
plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces)
523+
plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces)
547524
require.Error(t, err)
548525
require.ErrorContains(t, err, "apiServiceDefintions are not supported")
549526
require.Nil(t, plainBundle)
@@ -559,7 +536,7 @@ func convertToUnstructured(t *testing.T, obj interface{}) unstructured.Unstructu
559536
func findObjectByName(name string, result []client.Object) client.Object {
560537
for _, o := range result {
561538
// Since this is a controlled env, comparing only the names is sufficient for now.
562-
// In future, compare GVKs too by ensuring its set on the unstructuredObj.
539+
// In the future, compare GVKs too by ensuring its set on the unstructuredObj.
563540
if o.GetName() == name {
564541
return o
565542
}

0 commit comments

Comments
 (0)
Please sign in to comment.