Skip to content

Commit aa53bbc

Browse files
committed
fbc: promote package-level metdata from olm.csv.metadata to olm.package blob
Signed-off-by: Joe Lanford <joe.lanford@gmail.com>
1 parent b883133 commit aa53bbc

File tree

7 files changed

+264
-71
lines changed

7 files changed

+264
-71
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package migrations
2+
3+
import (
4+
"encoding/json"
5+
"slices"
6+
"strings"
7+
"unicode"
8+
"unicode/utf8"
9+
10+
"github.com/Masterminds/semver/v3"
11+
12+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
13+
14+
"github.com/operator-framework/operator-registry/alpha/declcfg"
15+
"github.com/operator-framework/operator-registry/alpha/property"
16+
)
17+
18+
func promotePackageMetadata(cfg *declcfg.DeclarativeConfig) error {
19+
metadataByPackage := map[string]promotedMetadata{}
20+
for i := range cfg.Bundles {
21+
b := &cfg.Bundles[i]
22+
23+
csvMetadata, csvMetadataIdx, err := getCsvMetadata(b)
24+
if err != nil {
25+
return err
26+
}
27+
28+
// Skip objects that have no olm.csv.metadata property
29+
if csvMetadata == nil {
30+
continue
31+
}
32+
33+
// Keep track of the metadata from the highest versioned bundle from each package.
34+
cur, ok := metadataByPackage[b.Package]
35+
if !ok || compareRegistryV1Semver(cur.version, b.Version) < 0 {
36+
metadataByPackage[b.Package] = promotedCSVMetadata(b.Version, csvMetadata)
37+
}
38+
39+
// Delete the package-level metadata from the olm.csv.metadata object and
40+
// update the bundle properties to use the new slimmed-down revision of it.
41+
csvMetadata.DisplayName = ""
42+
delete(csvMetadata.Annotations, "description")
43+
csvMetadata.Provider = v1alpha1.AppLink{}
44+
csvMetadata.Maintainers = nil
45+
csvMetadata.Links = nil
46+
csvMetadata.Keywords = nil
47+
48+
newCSVMetadata, err := json.Marshal(csvMetadata)
49+
if err != nil {
50+
return err
51+
}
52+
b.Properties[csvMetadataIdx] = property.Property{
53+
Type: property.TypeCSVMetadata,
54+
Value: newCSVMetadata,
55+
}
56+
}
57+
58+
// Update each olm.package object to include the metadata we extracted from
59+
// bundles in the first loop.
60+
for i := range cfg.Packages {
61+
pkg := &cfg.Packages[i]
62+
metadata, ok := metadataByPackage[pkg.Name]
63+
if !ok {
64+
continue
65+
}
66+
pkg.DisplayName = metadata.displayName
67+
pkg.ShortDescription = shortenDescription(metadata.shortDescription)
68+
if metadata.provider.Name != "" || metadata.provider.URL != "" {
69+
pkg.Provider = &metadata.provider
70+
}
71+
pkg.Maintainers = metadata.maintainers
72+
pkg.Links = metadata.links
73+
pkg.Keywords = slices.DeleteFunc(metadata.keywords, func(s string) bool {
74+
// Delete keywords that are empty strings
75+
return s == ""
76+
})
77+
}
78+
return nil
79+
}
80+
81+
func getCsvMetadata(b *declcfg.Bundle) (*property.CSVMetadata, int, error) {
82+
for i, p := range b.Properties {
83+
if p.Type != property.TypeCSVMetadata {
84+
continue
85+
}
86+
var csvMetadata property.CSVMetadata
87+
if err := json.Unmarshal(p.Value, &csvMetadata); err != nil {
88+
return nil, -1, err
89+
}
90+
return &csvMetadata, i, nil
91+
}
92+
return nil, -1, nil
93+
}
94+
95+
func compareRegistryV1Semver(a, b *semver.Version) int {
96+
if v := a.Compare(b); v != 0 {
97+
return v
98+
}
99+
aPre := semver.New(0, 0, 0, a.Metadata(), "")
100+
bPre := semver.New(0, 0, 0, b.Metadata(), "")
101+
return aPre.Compare(bPre)
102+
}
103+
104+
type promotedMetadata struct {
105+
version *semver.Version
106+
107+
displayName string
108+
shortDescription string
109+
provider v1alpha1.AppLink
110+
maintainers []v1alpha1.Maintainer
111+
links []v1alpha1.AppLink
112+
keywords []string
113+
}
114+
115+
func promotedCSVMetadata(version *semver.Version, metadata *property.CSVMetadata) promotedMetadata {
116+
return promotedMetadata{
117+
version: version,
118+
displayName: metadata.DisplayName,
119+
shortDescription: metadata.Annotations["description"],
120+
provider: metadata.Provider,
121+
maintainers: metadata.Maintainers,
122+
links: metadata.Links,
123+
keywords: metadata.Keywords,
124+
}
125+
}
126+
127+
func shortenDescription(input string) string {
128+
const maxLen = 256
129+
input = strings.TrimSpace(input)
130+
131+
// If the input is already under the limit return it.
132+
if utf8.RuneCountInString(input) <= maxLen {
133+
return input
134+
}
135+
136+
// Chop off everything after the first paragraph.
137+
if idx := strings.Index(input, "\n\n"); idx != -1 {
138+
input = strings.TrimSpace(input[:idx])
139+
}
140+
141+
// If we're _now_ under the limit, return the first paragraph.
142+
if utf8.RuneCountInString(input) <= maxLen {
143+
return input
144+
}
145+
146+
// If the first paragraph is still over the limit, we'll have to truncate.
147+
// We'll truncate at the last word boundary that still allows an ellipsis
148+
// to fit within the maximum length. But if there are no word boundaries
149+
// (veeeeery unlikely), we'll hard truncate mid-word.
150+
input = input[:maxLen-3]
151+
if truncatedIdx := strings.LastIndexFunc(input, unicode.IsSpace); truncatedIdx != -1 {
152+
return input[:truncatedIdx] + "..."
153+
}
154+
return input + "..."
155+
}

alpha/action/migrations/migrations.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var allMigrations = []Migration{
5656
newMigration("bundle-object-to-csv-metadata", `migrates bundles' "olm.bundle.object" to "olm.csv.metadata"`, bundleObjectToCSVMetadata),
5757
newMigration("split-icon", `split package icon out into separate "olm.icon" blob`, splitIcon),
5858
newMigration("promote-bundle-version", `promote bundle version into first-class bundle field, remove olm.package properties`, promoteBundleVersion),
59+
newMigration("promote-package-metadata", `promote package metadata from "olm.csv.metadata" properties to "olm.package" blob`, promotePackageMetadata),
5960
}
6061

6162
func NewMigrations(name string) (*Migrations, error) {

alpha/action/migrations/migrations_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ func TestMigrations(t *testing.T) {
3737
}
3838
return nil
3939
},
40-
MigrationToken("split-icon"): func(d *declcfg.DeclarativeConfig) error { return nil },
41-
MigrationToken("promote-bundle-version"): func(d *declcfg.DeclarativeConfig) error { return nil },
40+
MigrationToken("split-icon"): func(d *declcfg.DeclarativeConfig) error { return nil },
41+
MigrationToken("promote-bundle-version"): func(d *declcfg.DeclarativeConfig) error { return nil },
42+
MigrationToken("promote-package-metadata"): func(d *declcfg.DeclarativeConfig) error { return nil },
4243
}
4344

4445
tests := []struct {

alpha/declcfg/declcfg.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
utilerrors "k8s.io/apimachinery/pkg/util/errors"
1212
"k8s.io/apimachinery/pkg/util/sets"
1313

14+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
15+
1416
"github.com/operator-framework/operator-registry/alpha/property"
1517
prettyunmarshaler "github.com/operator-framework/operator-registry/pkg/prettyunmarshaler"
1618
)
@@ -33,9 +35,15 @@ type DeclarativeConfig struct {
3335
}
3436

3537
type Package struct {
36-
Schema string `json:"schema"`
37-
Name string `json:"name"`
38-
DefaultChannel string `json:"defaultChannel"`
38+
Schema string `json:"schema"`
39+
Name string `json:"name"`
40+
DisplayName string `json:"displayName,omitempty"`
41+
ShortDescription string `json:"shortDescription,omitempty"`
42+
Provider *v1alpha1.AppLink `json:"provider,omitempty"`
43+
Maintainers []v1alpha1.Maintainer `json:"maintainers,omitempty"`
44+
Links []v1alpha1.AppLink `json:"links,omitempty"`
45+
Keywords []string `json:"keywords,omitempty"`
46+
DefaultChannel string `json:"defaultChannel"`
3947

4048
// Deprecated: It is no longer recommended to embed an icon in the package.
4149
// Instead, use separate a Icon item alongside the Package.

alpha/declcfg/declcfg_to_model.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) {
3232
Description: p.Description,
3333
Channels: map[string]*model.Channel{},
3434

35-
//DisplayName: p.DisplayName,
36-
//ShortDescription: p.ShortDescription,
37-
//Provider: p.Provider,
38-
//Maintainers: p.Maintainers,
39-
//Links: p.Links,
40-
//Keywords: p.Keywords,
35+
DisplayName: p.DisplayName,
36+
ShortDescription: p.ShortDescription,
37+
Provider: p.Provider,
38+
Maintainers: p.Maintainers,
39+
Links: p.Links,
40+
Keywords: p.Keywords,
4141
}
4242
if p.Icon != nil {
4343
mpkg.Icon = &model.Icon{

alpha/model/model.go

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"golang.org/x/exp/maps"
1515
"k8s.io/apimachinery/pkg/util/sets"
1616

17+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
18+
1719
"github.com/operator-framework/operator-registry/alpha/property"
1820
)
1921

@@ -52,12 +54,12 @@ type Package struct {
5254
Channels map[string]*Channel
5355
Deprecation *Deprecation
5456

55-
//DisplayName string
56-
//ShortDescription string
57-
//Provider v1alpha1.AppLink
58-
//Maintainers []v1alpha1.Maintainer
59-
//Links []v1alpha1.AppLink
60-
//Keywords []string
57+
DisplayName string
58+
ShortDescription string
59+
Provider *v1alpha1.AppLink
60+
Maintainers []v1alpha1.Maintainer
61+
Links []v1alpha1.AppLink
62+
Keywords []string
6163
}
6264

6365
func (m *Package) Validate() error {
@@ -67,25 +69,25 @@ func (m *Package) Validate() error {
6769
result.subErrors = append(result.subErrors, errors.New("package name must not be empty"))
6870
}
6971

70-
//if len(m.ShortDescription) > 128 {
71-
// result.subErrors = append(result.subErrors, errors.New("short description must not be more than 128 characters"))
72-
//}
73-
//
74-
//for i, maintainer := range m.Maintainers {
75-
// if maintainer.Name == "" && maintainer.Email == "" {
76-
// result.subErrors = append(result.subErrors, fmt.Errorf("maintainer at index %d must not be empty", i))
77-
// }
78-
//}
79-
//for i, link := range m.Links {
80-
// if link.Name == "" && link.URL == "" {
81-
// result.subErrors = append(result.subErrors, fmt.Errorf("link at index %d must not be empty", i))
82-
// }
83-
//}
84-
//for i, keyword := range m.Keywords {
85-
// if keyword == "" {
86-
// result.subErrors = append(result.subErrors, fmt.Errorf("keyword at index %d must not be empty", i))
87-
// }
88-
//}
72+
if len(m.ShortDescription) > 256 {
73+
result.subErrors = append(result.subErrors, fmt.Errorf("short description must not be more than 128 characters, found %d characters", len(m.ShortDescription)))
74+
}
75+
76+
for i, maintainer := range m.Maintainers {
77+
if maintainer.Name == "" && maintainer.Email == "" {
78+
result.subErrors = append(result.subErrors, fmt.Errorf("maintainer at index %d must not be empty", i))
79+
}
80+
}
81+
for i, link := range m.Links {
82+
if link.Name == "" && link.URL == "" {
83+
result.subErrors = append(result.subErrors, fmt.Errorf("link at index %d must not be empty", i))
84+
}
85+
}
86+
for i, keyword := range m.Keywords {
87+
if keyword == "" {
88+
result.subErrors = append(result.subErrors, fmt.Errorf("keyword at index %d must not be empty", i))
89+
}
90+
}
8991

9092
if err := m.Icon.Validate(); err != nil {
9193
result.subErrors = append(result.subErrors, err)

0 commit comments

Comments
 (0)