Skip to content

Commit

Permalink
Adds new catalogmetadata package
Browse files Browse the repository at this point in the history
At the moment it contains:
* Types which embed `declcfg.Package`, `declcfg.Channel` and `declcfg.Bundle`.
  Later we will be adding more methods to them (for unmarshalling properties, for example)
* A function to fetch metadata by scheme & catalog name
* `Filter` function which can filter all these 3 types using a given predicate
* `And`, `Or` and `Not` composite predicates to be used with `Filter`

Later we might expand/change this package to be more convinient in use with variable sources.

Signed-off-by: Mikalai Radchuk <mradchuk@redhat.com>
  • Loading branch information
m1kola committed Sep 5, 2023
1 parent 751d4f6 commit 3cd59a2
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 0 deletions.
47 changes: 47 additions & 0 deletions internal/catalogmetadata/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package catalogmetadata

// Predicate returns true if the object should be kept when filtering
type Predicate[T Schemas] func(entity *T) bool

// Filter filters a slice accordingly to
func Filter[T Schemas](in []*T, test Predicate[T]) []*T {
out := []*T{}
for i := range in {
if test(in[i]) {
out = append(out, in[i])
}
}
return out
}

func And[T Schemas](predicates ...Predicate[T]) Predicate[T] {
return func(obj *T) bool {
eval := true
for _, predicate := range predicates {
eval = eval && predicate(obj)
if !eval {
return false
}
}
return eval
}
}

func Or[T Schemas](predicates ...Predicate[T]) Predicate[T] {
return func(obj *T) bool {
eval := false
for _, predicate := range predicates {
eval = eval || predicate(obj)
if eval {
return true
}
}
return eval
}
}

func Not[T Schemas](predicate Predicate[T]) Predicate[T] {
return func(obj *T) bool {
return !predicate(obj)
}
}
78 changes: 78 additions & 0 deletions internal/catalogmetadata/filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package catalogmetadata_test

import (
"testing"

"github.com/operator-framework/operator-registry/alpha/declcfg"
"github.com/stretchr/testify/assert"

"github.com/operator-framework/operator-controller/internal/catalogmetadata"
)

func TestFilter(t *testing.T) {
in := []*catalogmetadata.Bundle{
{Bundle: declcfg.Bundle{Name: "operator1.v1", Package: "operator1", Image: "fake1"}},
{Bundle: declcfg.Bundle{Name: "operator1.v2", Package: "operator1", Image: "fake2"}},
{Bundle: declcfg.Bundle{Name: "operator2.v1", Package: "operator2", Image: "fake1"}},
}

for _, tt := range []struct {
name string
predicate catalogmetadata.Predicate[catalogmetadata.Bundle]
want []*catalogmetadata.Bundle
}{
{
name: "simple filter with one predicate",
predicate: func(bundle *catalogmetadata.Bundle) bool {
return bundle.Name == "operator1.v1"
},
want: []*catalogmetadata.Bundle{
{declcfg.Bundle{Name: "operator1.v1", Package: "operator1", Image: "fake1"}},
},
},
{
name: "filter with Not predicate",
predicate: catalogmetadata.Not(func(bundle *catalogmetadata.Bundle) bool {
return bundle.Name == "operator1.v1"
}),
want: []*catalogmetadata.Bundle{
{declcfg.Bundle{Name: "operator1.v2", Package: "operator1", Image: "fake2"}},
{declcfg.Bundle{Name: "operator2.v1", Package: "operator2", Image: "fake1"}},
},
},
{
name: "filter with And predicate",
predicate: catalogmetadata.And(
func(bundle *catalogmetadata.Bundle) bool {
return bundle.Name == "operator1.v1"
},
func(bundle *catalogmetadata.Bundle) bool {
return bundle.Image == "fake1"
},
),
want: []*catalogmetadata.Bundle{
{declcfg.Bundle{Name: "operator1.v1", Package: "operator1", Image: "fake1"}},
},
},
{
name: "filter with Or predicate",
predicate: catalogmetadata.Or(
func(bundle *catalogmetadata.Bundle) bool {
return bundle.Name == "operator1.v1"
},
func(bundle *catalogmetadata.Bundle) bool {
return bundle.Image == "fake1"
},
),
want: []*catalogmetadata.Bundle{
{declcfg.Bundle{Name: "operator1.v1", Package: "operator1", Image: "fake1"}},
{declcfg.Bundle{Name: "operator2.v1", Package: "operator2", Image: "fake1"}},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
actual := catalogmetadata.Filter(in, tt.predicate)
assert.Equal(t, tt.want, actual)
})
}
}
21 changes: 21 additions & 0 deletions internal/catalogmetadata/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package catalogmetadata

import (
"github.com/operator-framework/operator-registry/alpha/declcfg"
)

type Schemas interface {
Package | Bundle | Channel
}

type Package struct {
declcfg.Package
}

type Channel struct {
declcfg.Channel
}

type Bundle struct {
declcfg.Bundle
}
20 changes: 20 additions & 0 deletions internal/catalogmetadata/unmarshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package catalogmetadata

import (
"encoding/json"

catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
)

func Unmarshal[T Schemas](cm []catalogd.CatalogMetadata) ([]*T, error) {
contents := make([]*T, 0, len(cm))
for _, cm := range cm {
var content T
if err := json.Unmarshal(cm.Spec.Content, &content); err != nil {
return nil, err
}
contents = append(contents, &content)
}

return contents, nil
}
110 changes: 110 additions & 0 deletions internal/catalogmetadata/unmarshal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package catalogmetadata_test

import (
"encoding/json"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"

catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/operator-registry/alpha/declcfg"
"github.com/operator-framework/operator-registry/alpha/property"
"github.com/stretchr/testify/assert"

"github.com/operator-framework/operator-controller/internal/catalogmetadata"
)

var (
scheme *runtime.Scheme
)

func init() {
scheme = runtime.NewScheme()
utilruntime.Must(catalogd.AddToScheme(scheme))
}

func TestFetchByScheme(t *testing.T) {
fakeCatalogName := "fake-catalog"

validBundle := `{
"schema": "olm.bundle",
"name": "fake1.v1.0.0",
"package": "fake1",
"image": "fake-image",
"properties": [
{
"type": "olm.package",
"value": {"packageName":"fake1","version":"1.0.0"}
}
]
}`

for _, tt := range []struct {
name string
objs []catalogd.CatalogMetadata
wantData []*catalogmetadata.Bundle
wantErr string
}{
{
name: "valid objects",
objs: []catalogd.CatalogMetadata{
{
ObjectMeta: metav1.ObjectMeta{
Name: "obj-1",
Labels: map[string]string{"schema": declcfg.SchemaBundle, "catalog": fakeCatalogName},
},
Spec: catalogd.CatalogMetadataSpec{
Content: json.RawMessage(validBundle),
},
},
},
wantData: []*catalogmetadata.Bundle{
{
Bundle: declcfg.Bundle{
Schema: declcfg.SchemaBundle,
Name: "fake1.v1.0.0",
Package: "fake1",
Image: "fake-image",
Properties: []property.Property{
{
Type: property.TypePackage,
Value: json.RawMessage(`{"packageName":"fake1","version":"1.0.0"}`),
},
},
},
},
},
},
{
name: "invalid objects",
objs: []catalogd.CatalogMetadata{
{
ObjectMeta: metav1.ObjectMeta{
Name: "obj-1",
Labels: map[string]string{"schema": declcfg.SchemaBundle, "catalog": fakeCatalogName},
},
Spec: catalogd.CatalogMetadataSpec{
Content: json.RawMessage(`{"name":123123123}`),
},
},
},
wantErr: "json: cannot unmarshal number into Go struct field Bundle.name of type string",
},
{
name: "not found",
wantData: []*catalogmetadata.Bundle{},
},
} {
t.Run(tt.name, func(t *testing.T) {
data, err := catalogmetadata.Unmarshal[catalogmetadata.Bundle](tt.objs)
assert.Equal(t, tt.wantData, data)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}

0 comments on commit 3cd59a2

Please sign in to comment.