Skip to content

Commit

Permalink
(feat): add graph deprecation logic
Browse files Browse the repository at this point in the history
to signal when an installed bundle has deprecations
associated with it and prefer bundles with deprecations
less than non-deprecated bundles

Signed-off-by: everettraven <everettraven@gmail.com>
  • Loading branch information
everettraven committed Jan 11, 2024
1 parent 457010e commit 33de0a6
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 10 deletions.
12 changes: 12 additions & 0 deletions api/v1alpha1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ const (
// TODO(user): add more Types, here and into init()
TypeInstalled = "Installed"
TypeResolved = "Resolved"
// TypeDeprecated is a rollup condition that is present when
// any of the deprecated conditions are present.
TypeDeprecated = "Deprecated"
TypePackageDeprecated = "PackageDeprecated"
TypeChannelDeprecated = "ChannelDeprecated"
TypeBundleDeprecated = "BundleDeprecated"

ReasonBundleLookupFailed = "BundleLookupFailed"
ReasonInstallationFailed = "InstallationFailed"
Expand All @@ -80,13 +86,18 @@ const (
ReasonResolutionFailed = "ResolutionFailed"
ReasonResolutionUnknown = "ResolutionUnknown"
ReasonSuccess = "Success"
ReasonDeprecated = "Deprecated"
)

func init() {
// TODO(user): add Types from above
conditionsets.ConditionTypes = append(conditionsets.ConditionTypes,
TypeInstalled,
TypeResolved,
TypeDeprecated,
TypePackageDeprecated,
TypeChannelDeprecated,
TypeBundleDeprecated,
)
// TODO(user): add Reasons from above
conditionsets.ConditionReasons = append(conditionsets.ConditionReasons,
Expand All @@ -98,6 +109,7 @@ func init() {
ReasonInstallationStatusUnknown,
ReasonInvalidSpec,
ReasonSuccess,
ReasonDeprecated,
)
}

Expand Down
13 changes: 10 additions & 3 deletions cmd/resolutioncli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ func (c *indexRefClient) Bundles(ctx context.Context) ([]*catalogmetadata.Bundle
}

var (
channels []*catalogmetadata.Channel
bundles []*catalogmetadata.Bundle
channels []*catalogmetadata.Channel
bundles []*catalogmetadata.Bundle
deprecations []*catalogmetadata.Deprecation
)

for i := range cfg.Channels {
Expand All @@ -63,10 +64,16 @@ func (c *indexRefClient) Bundles(ctx context.Context) ([]*catalogmetadata.Bundle
})
}

for i := range cfg.Deprecations {
deprecations = append(deprecations, &catalogmetadata.Deprecation{
Deprecation: cfg.Deprecations[i],
})
}

// TODO: update fake catalog name string to be catalog name once we support multiple catalogs in CLI
catalogName := "offline-catalog"

bundles, err = client.PopulateExtraFields(catalogName, channels, bundles)
bundles, err = client.PopulateExtraFields(catalogName, channels, bundles, deprecations)
if err != nil {
return nil, err
}
Expand Down
41 changes: 39 additions & 2 deletions internal/catalogmetadata/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (c *Client) Bundles(ctx context.Context) ([]*catalogmetadata.Bundle, error)
}
channels := []*catalogmetadata.Channel{}
bundles := []*catalogmetadata.Bundle{}
deprecations := []*catalogmetadata.Deprecation{}

rc, err := c.fetcher.FetchCatalogContents(ctx, catalog.DeepCopy())
if err != nil {
Expand All @@ -81,6 +82,12 @@ func (c *Client) Bundles(ctx context.Context) ([]*catalogmetadata.Bundle, error)
return fmt.Errorf("error unmarshalling bundle from catalog metadata: %s", err)
}
bundles = append(bundles, &content)
case declcfg.SchemaDeprecation:
var content catalogmetadata.Deprecation
if err := json.Unmarshal(meta.Blob, &content); err != nil {
return fmt.Errorf("error unmarshalling deprecation from catalog metadata: %s", err)
}
deprecations = append(deprecations, &content)
}

return nil
Expand All @@ -89,7 +96,7 @@ func (c *Client) Bundles(ctx context.Context) ([]*catalogmetadata.Bundle, error)
return nil, fmt.Errorf("error processing response: %s", err)
}

bundles, err = PopulateExtraFields(catalog.Name, channels, bundles)
bundles, err = PopulateExtraFields(catalog.Name, channels, bundles, deprecations)
if err != nil {
return nil, err
}
Expand All @@ -100,7 +107,7 @@ func (c *Client) Bundles(ctx context.Context) ([]*catalogmetadata.Bundle, error)
return allBundles, nil
}

func PopulateExtraFields(catalogName string, channels []*catalogmetadata.Channel, bundles []*catalogmetadata.Bundle) ([]*catalogmetadata.Bundle, error) {
func PopulateExtraFields(catalogName string, channels []*catalogmetadata.Channel, bundles []*catalogmetadata.Bundle, deprecations []*catalogmetadata.Deprecation) ([]*catalogmetadata.Bundle, error) {
bundlesMap := map[string]*catalogmetadata.Bundle{}
for i := range bundles {
bundleKey := fmt.Sprintf("%s-%s", bundles[i].Package, bundles[i].Name)
Expand All @@ -121,5 +128,35 @@ func PopulateExtraFields(catalogName string, channels []*catalogmetadata.Channel
}
}

// According to https://docs.google.com/document/d/1EzefSzoGZL2ipBt-eCQwqqNwlpOIt7wuwjG6_8ZCi5s/edit?usp=sharing
// the olm.deprecations FBC object is only valid when either 0 or 1 instances exist
// for any given package
deprecationMap := make(map[string]*catalogmetadata.Deprecation, len(deprecations))
for _, deprecation := range deprecations {
deprecationMap[deprecation.Package] = deprecation
}

for i := range bundles {
if dep, ok := deprecationMap[bundles[i].Package]; ok {
for _, entry := range dep.Entries {
switch entry.Reference.Schema {
case declcfg.SchemaPackage:
bundles[i].Deprecations = append(bundles[i].Deprecations, entry)
case declcfg.SchemaChannel:
for _, ch := range bundles[i].InChannels {
if ch.Name == entry.Reference.Name {
bundles[i].Deprecations = append(bundles[i].Deprecations, entry)
break
}
}
case declcfg.SchemaBundle:
if bundles[i].Name == entry.Reference.Name {
bundles[i].Deprecations = append(bundles[i].Deprecations, entry)
}
}
}
}
}

return bundles, nil
}
39 changes: 39 additions & 0 deletions internal/catalogmetadata/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,45 @@ func TestClient(t *testing.T) {
},
fetcher: &MockFetcher{},
},
{
name: "deprecated at the package, channel, and bundle level",
fakeCatalog: func() ([]client.Object, []*catalogmetadata.Bundle, map[string][]byte) {
objs, bundles, catalogContentMap := defaultFakeCatalog()

catalogContentMap["catalog-1"] = append(catalogContentMap["catalog-1"],
[]byte(`{"schema": "olm.deprecations", "package":"fake1", "entries":[{"message": "fake1 is deprecated", "reference": {"schema": "olm.package"}}, {"message":"channel stable is deprecated", "reference": {"schema": "olm.channel", "name": "stable"}}, {"message": "bundle fake1.v1.0.0 is deprecated", "reference":{"schema":"olm.bundle", "name":"fake1.v1.0.0"}}]}`)...)

for i := range bundles {
if bundles[i].Package == "fake1" && bundles[i].CatalogName == "catalog-1" && bundles[i].Name == "fake1.v1.0.0" {
bundles[i].Deprecations = append(bundles[i].Deprecations, declcfg.DeprecationEntry{
Reference: declcfg.PackageScopedReference{
Schema: "olm.package",
},
Message: "fake1 is deprecated",
})

bundles[i].Deprecations = append(bundles[i].Deprecations, declcfg.DeprecationEntry{
Reference: declcfg.PackageScopedReference{
Schema: "olm.channel",
Name: "stable",
},
Message: "channel stable is deprecated",
})

bundles[i].Deprecations = append(bundles[i].Deprecations, declcfg.DeprecationEntry{
Reference: declcfg.PackageScopedReference{
Schema: "olm.bundle",
Name: "fake1.v1.0.0",
},
Message: "bundle fake1.v1.0.0 is deprecated",
})
}
}

return objs, bundles, catalogContentMap
},
fetcher: &MockFetcher{},
},
} {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
Expand Down
6 changes: 6 additions & 0 deletions internal/catalogmetadata/filter/bundle_predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,9 @@ func Replaces(bundleName string) Predicate[catalogmetadata.Bundle] {
return false
}
}

func WithDeprecation(deprecated bool) Predicate[catalogmetadata.Bundle] {
return func(bundle *catalogmetadata.Bundle) bool {
return bundle.HasDeprecation() == deprecated
}
}
16 changes: 16 additions & 0 deletions internal/catalogmetadata/filter/bundle_predicates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,19 @@ func TestReplaces(t *testing.T) {
assert.False(t, f(b2))
assert.False(t, f(b3))
}

func TestWithDeprecation(t *testing.T) {
b1 := &catalogmetadata.Bundle{
Deprecations: []declcfg.DeprecationEntry{
{
Reference: declcfg.PackageScopedReference{},
},
},
}

b2 := &catalogmetadata.Bundle{}

f := filter.WithDeprecation(true)
assert.True(t, f(b1))
assert.False(t, f(b2))
}
19 changes: 19 additions & 0 deletions internal/catalogmetadata/sort/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ func ByVersion(b1, b2 *catalogmetadata.Bundle) bool {
return ver1.GT(*ver2)
}

// ByDeprecation is a sort "less" function that orders bundles
// with deprecations lower than ones without deprecations
func ByDeprecation(b1, b2 *catalogmetadata.Bundle) bool {
b1Val := 1
b2Val := 1

if b1.HasDeprecation() {
b1Val = b1Val - 1
}

if b2.HasDeprecation() {
b2Val = b2Val - 1
}

// Check for "greater than" because we
// non deprecated on top
return b1Val > b2Val
}

// compareErrors returns 0 if both errors are either nil or not nil
// -1 if err1 is nil and err2 is not nil
// +1 if err1 is not nil and err2 is nil
Expand Down
31 changes: 31 additions & 0 deletions internal/catalogmetadata/sort/sort_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/operator-framework/operator-registry/alpha/declcfg"
"github.com/operator-framework/operator-registry/alpha/property"
Expand Down Expand Up @@ -81,3 +82,33 @@ func TestByVersion(t *testing.T) {
assert.Equal(t, b5empty, toSort[4])
})
}

func TestByDeprecation(t *testing.T) {
b1 := &catalogmetadata.Bundle{
CatalogName: "foo",
Bundle: declcfg.Bundle{
Name: "bar",
},
}

b2 := &catalogmetadata.Bundle{
CatalogName: "foo",
Bundle: declcfg.Bundle{
Name: "baz",
},
Deprecations: []declcfg.DeprecationEntry{
{
Reference: declcfg.PackageScopedReference{},
},
},
}

toSort := []*catalogmetadata.Bundle{b2, b1}
sort.SliceStable(toSort, func(i, j int) bool {
return catalogsort.ByDeprecation(toSort[i], toSort[j])
})

require.Len(t, toSort, 2)
assert.Equal(t, b1, toSort[0])
assert.Equal(t, b2, toSort[1])
}
15 changes: 12 additions & 3 deletions internal/catalogmetadata/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const (
)

type Schemas interface {
Package | Bundle | Channel
Package | Bundle | Channel | Deprecation
}

type Package struct {
Expand All @@ -29,15 +29,20 @@ type Channel struct {
declcfg.Channel
}

type Deprecation struct {
declcfg.Deprecation
}

type PackageRequired struct {
property.PackageRequired
SemverRange bsemver.Range `json:"-"`
}

type Bundle struct {
declcfg.Bundle
CatalogName string
InChannels []*Channel
CatalogName string
InChannels []*Channel
Deprecations []declcfg.DeprecationEntry

mu sync.RWMutex
// these properties are lazy loaded as they are requested
Expand Down Expand Up @@ -140,6 +145,10 @@ func (b *Bundle) propertiesByType(propType string) []*property.Property {
return b.propertiesMap[propType]
}

func (b *Bundle) HasDeprecation() bool {
return len(b.Deprecations) > 0
}

func loadOneFromProps[T any](bundle *Bundle, propType string, required bool) (T, error) {
r, err := loadFromProps[T](bundle, propType, required)
if err != nil {
Expand Down
28 changes: 28 additions & 0 deletions internal/catalogmetadata/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,31 @@ func TestBundleMediaType(t *testing.T) {
})
}
}

func TestBundleHasDeprecation(t *testing.T) {
for _, tt := range []struct {
name string
bundle *catalogmetadata.Bundle
deprecated bool
}{
{
name: "has deprecation entries",
bundle: &catalogmetadata.Bundle{
Deprecations: []declcfg.DeprecationEntry{
{
Reference: declcfg.PackageScopedReference{},
},
},
},
deprecated: true,
},
{
name: "has no deprecation entries",
bundle: &catalogmetadata.Bundle{},
},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.deprecated, tt.bundle.HasDeprecation())
})
}
}
Loading

0 comments on commit 33de0a6

Please sign in to comment.