@@ -5,15 +5,26 @@ import (
55 "encoding/json"
66 "errors"
77 "fmt"
8+ "strings"
89
10+ "github.com/blang/semver/v4"
911 "golang.org/x/text/cases"
1012 utilerrors "k8s.io/apimachinery/pkg/util/errors"
1113 "k8s.io/apimachinery/pkg/util/sets"
1214
15+ "github.com/operator-framework/operator-registry/alpha/model"
1316 "github.com/operator-framework/operator-registry/alpha/property"
1417 prettyunmarshaler "github.com/operator-framework/operator-registry/pkg/prettyunmarshaler"
1518)
1619
20+ // Re-export VersionRelease/Release types/functions from model package to make it possible for users to only include this package and avoid import cycles
21+ type (
22+ Release = model.Release
23+ VersionRelease = model.VersionRelease
24+ )
25+
26+ var NewRelease = model .NewRelease
27+
1728const (
1829 SchemaPackage = "olm.package"
1930 SchemaChannel = "olm.channel"
@@ -206,3 +217,76 @@ func (destination *DeclarativeConfig) Merge(src *DeclarativeConfig) {
206217 destination .Others = append (destination .Others , src .Others ... )
207218 destination .Deprecations = append (destination .Deprecations , src .Deprecations ... )
208219}
220+
221+ // order by version, then
222+ // release, if present
223+ func (b * Bundle ) Compare (other * Bundle ) int {
224+ if b .Name == other .Name {
225+ return 0
226+ }
227+ avr , err := b .VersionRelease ()
228+ if err != nil {
229+ return 0
230+ }
231+ otherVr , err := other .VersionRelease ()
232+ if err != nil {
233+ return 0
234+ }
235+ return avr .Compare (otherVr )
236+ }
237+
238+ // constructs a VersionRelease from the olm.package property of the bundle
239+ // this handles the cases where the property is present, missing, or duplicated
240+ // if a release field is present in the property, it is used as-is
241+ // if it is NOT present in the property, but the version field contains build metadata,
242+ // we attempt to convert the build metadata into a release and strip the build metadata from the version.
243+ // This is to support bundles that use the legacy approach of encoding release information in the build metadata field of the version
244+ func (b * Bundle ) VersionRelease () (* VersionRelease , error ) {
245+ var (
246+ vr * VersionRelease
247+ )
248+ // loop over all properties, and do not break if we find a package property, in order to check for duplicates
249+ for _ , prop := range b .Properties {
250+ switch prop .Type {
251+ case property .TypePackage :
252+ var p property.Package
253+
254+ // if we encounter more than one olm.package property, return an error
255+ if vr != nil {
256+ return nil , fmt .Errorf ("must be exactly one property of type %q" , SchemaPackage )
257+ }
258+
259+ if err := json .Unmarshal (prop .Value , & p ); err != nil {
260+ return nil , fmt .Errorf ("unable to unmarshal \" olm.package\" property for bundle %q: %v" , b .Name , err )
261+ }
262+ pv , err := semver .Parse (p .Version )
263+ if err != nil {
264+ return nil , fmt .Errorf ("invalid semver version %q in \" olm.package\" property for bundle %q: %v" , p .Version , b .Name , err )
265+ }
266+ pr , err := NewRelease (p .Release )
267+ if err != nil {
268+ return nil , fmt .Errorf ("invalid release %q in \" olm.package\" property for bundle %q: %v" , p .Release , b .Name , err )
269+ }
270+ vr = & VersionRelease {
271+ Version : pv ,
272+ Release : pr ,
273+ }
274+ }
275+ }
276+ if vr == nil {
277+ return nil , fmt .Errorf ("no \" olm.package\" property found for bundle %q" , b .Name )
278+ }
279+
280+ // if the bundle's release isn't provided, see if we can use the legacy build metadata release approach to identify a release
281+ // if successful, remove the build metadata from the version
282+ if len (vr .Release ) == 0 && vr .Version .Build != nil {
283+ newrel , err := NewRelease (strings .Join (vr .Version .Build , "." ))
284+ if err != nil {
285+ return nil , fmt .Errorf ("unable to convert build metadata to release for bundle %q: %v" , b .Name , err )
286+ }
287+ vr .Release = newrel
288+ vr .Version .Build = nil
289+ }
290+
291+ return vr , nil
292+ }
0 commit comments