Skip to content

Commit b883133

Browse files
committed
fbc: promote bundle version from property to first-class field
Signed-off-by: Joe Lanford <joe.lanford@gmail.com>
1 parent b1a7ca3 commit b883133

File tree

12 files changed

+171
-35
lines changed

12 files changed

+171
-35
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package migrations
2+
3+
import (
4+
"encoding/json"
5+
"slices"
6+
7+
"github.com/Masterminds/semver/v3"
8+
9+
"github.com/operator-framework/operator-registry/alpha/declcfg"
10+
"github.com/operator-framework/operator-registry/alpha/property"
11+
)
12+
13+
func promoteBundleVersion(cfg *declcfg.DeclarativeConfig) error {
14+
promoteVersion := func(b *declcfg.Bundle) error {
15+
// Promote the version from the olm.package property to the bundle field.
16+
for _, p := range b.Properties {
17+
if p.Type != property.TypePackage {
18+
continue
19+
}
20+
var pkg property.Package
21+
if err := json.Unmarshal(p.Value, &pkg); err != nil {
22+
return err
23+
}
24+
version, err := semver.StrictNewVersion(pkg.Version)
25+
if err != nil {
26+
return err
27+
}
28+
b.Version = version
29+
}
30+
31+
// Delete the olm.package properties
32+
b.Properties = slices.DeleteFunc(b.Properties, func(p property.Property) bool {
33+
return p.Type == property.TypePackage
34+
})
35+
return nil
36+
}
37+
38+
for i := range cfg.Bundles {
39+
if err := promoteVersion(&cfg.Bundles[i]); err != nil {
40+
return err
41+
}
42+
}
43+
return nil
44+
}

alpha/action/migrations/migrations.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var allMigrations = []Migration{
5555
newMigration(NoMigrations, "do nothing", func(_ *declcfg.DeclarativeConfig) error { return nil }),
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),
58+
newMigration("promote-bundle-version", `promote bundle version into first-class bundle field, remove olm.package properties`, promoteBundleVersion),
5859
}
5960

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

alpha/action/migrations/migrations_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ func TestMigrations(t *testing.T) {
3737
}
3838
return nil
3939
},
40-
MigrationToken("split-icon"): 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 },
4142
}
4243

4344
tests := []struct {

alpha/declcfg/declcfg.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88

9+
"github.com/Masterminds/semver/v3"
910
"golang.org/x/text/cases"
1011
utilerrors "k8s.io/apimachinery/pkg/util/errors"
1112
"k8s.io/apimachinery/pkg/util/sets"
@@ -83,8 +84,9 @@ type ChannelEntry struct {
8384
// evaluation in bundlesEqual().
8485
type Bundle struct {
8586
Schema string `json:"schema"`
86-
Name string `json:"name,omitempty"`
87-
Package string `json:"package,omitempty"`
87+
Name string `json:"name"`
88+
Package string `json:"package"`
89+
Version *semver.Version `json:"version,omitempty"`
8890
Image string `json:"image"`
8991
Properties []property.Property `json:"properties,omitempty" hash:"set"`
9092
RelatedImages []RelatedImage `json:"relatedImages,omitempty" hash:"set"`

alpha/declcfg/declcfg_to_model.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,18 @@ func ConvertToModel(cfg DeclarativeConfig) (model.Model, error) {
140140
return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err)
141141
}
142142

143-
if len(props.Packages) != 1 {
144-
return nil, fmt.Errorf("package %q bundle %q must have exactly 1 %q property, found %d", b.Package, b.Name, property.TypePackage, len(props.Packages))
145-
}
146-
147-
if b.Package != props.Packages[0].PackageName {
148-
return nil, fmt.Errorf("package %q does not match %q property %q", b.Package, property.TypePackage, props.Packages[0].PackageName)
143+
var rawVersion string
144+
if b.Version != nil {
145+
rawVersion = b.Version.String()
146+
} else if len(props.Packages) == 1 {
147+
if b.Package != props.Packages[0].PackageName {
148+
return nil, fmt.Errorf("package %q does not match %q property %q", b.Package, property.TypePackage, props.Packages[0].PackageName)
149+
}
150+
rawVersion = props.Packages[0].Version
151+
} else {
152+
return nil, fmt.Errorf("package %q bundle %q requires either version being set or exactly one property with type %q", b.Package, b.Name, property.TypePackage)
149153
}
150154

151-
// Parse version from the package property.
152-
rawVersion := props.Packages[0].Version
153155
ver, err := semver.Parse(rawVersion)
154156
if err != nil {
155157
return nil, fmt.Errorf("error parsing bundle %q version %q: %v", b.Name, rawVersion, err)

alpha/declcfg/declcfg_to_model_test.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"testing"
66

7+
mmsemver "github.com/Masterminds/semver/v3"
78
"github.com/blang/semver/v4"
89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
@@ -120,16 +121,16 @@ func TestConvertToModel(t *testing.T) {
120121
},
121122
},
122123
{
123-
name: "Error/BundleMissingPackageProperty",
124-
assertion: hasError(`package "foo" bundle "foo.v0.1.0" must have exactly 1 "olm.package" property, found 0`),
124+
name: "Error/BundleMissingVersionAndPackageProperty",
125+
assertion: hasError(`package "foo" bundle "foo.v0.1.0" requires either version being set or exactly one property with type "olm.package"`),
125126
cfg: DeclarativeConfig{
126127
Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)},
127128
Bundles: []Bundle{newTestBundle("foo", "0.1.0", withNoProperties())},
128129
},
129130
},
130131
{
131132
name: "Error/BundleMultiplePackageProperty",
132-
assertion: hasError(`package "foo" bundle "foo.v0.1.0" must have exactly 1 "olm.package" property, found 2`),
133+
assertion: hasError(`package "foo" bundle "foo.v0.1.0" requires either version being set or exactly one property with type "olm.package"`),
133134
cfg: DeclarativeConfig{
134135
Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)},
135136
Bundles: []Bundle{newTestBundle("foo", "0.1.0", func(b *Bundle) {
@@ -140,6 +141,15 @@ func TestConvertToModel(t *testing.T) {
140141
})},
141142
},
142143
},
144+
{
145+
name: "Success/BundleWithVersionButNoProperties",
146+
assertion: require.NoError,
147+
cfg: DeclarativeConfig{
148+
Packages: []Package{newTestPackage("foo", "alpha", svgSmallCircle)},
149+
Channels: []Channel{newTestChannel("foo", "alpha", ChannelEntry{Name: testBundleName("foo", "0.1.0")})},
150+
Bundles: []Bundle{newTestBundle("foo", "0.1.0", withVersionField(mmsemver.MustParse("0.1.0")), withNoProperties())},
151+
},
152+
},
143153
{
144154
name: "Success/BundleWithDataButMissingImage",
145155
assertion: require.NoError,

alpha/declcfg/helpers_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import (
66
"sort"
77
"testing"
88

9-
"github.com/blang/semver/v4"
9+
mmsemver "github.com/Masterminds/semver/v3"
10+
bsemver "github.com/blang/semver/v4"
1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213

@@ -126,6 +127,12 @@ func buildValidDeclarativeConfig(spec validDeclarativeConfigSpec) DeclarativeCon
126127

127128
type bundleOpt func(*Bundle)
128129

130+
func withVersionField(version *mmsemver.Version) func(*Bundle) {
131+
return func(bundle *Bundle) {
132+
bundle.Version = version
133+
}
134+
}
135+
129136
func withNoProperties() func(*Bundle) {
130137
return func(b *Bundle) {
131138
b.Properties = []property.Property{}
@@ -147,6 +154,7 @@ func withNoBundleData() func(*Bundle) {
147154

148155
func newTestBundle(packageName, version string, opts ...bundleOpt) Bundle {
149156
csvJSON := fmt.Sprintf(`{"kind": "ClusterServiceVersion", "apiVersion": "operators.coreos.com/v1alpha1", "metadata":{"name":%q}}`, testBundleName(packageName, version))
157+
150158
b := Bundle{
151159
Schema: SchemaBundle,
152160
Name: testBundleName(packageName, version),
@@ -245,7 +253,7 @@ func getBundle(pkg *model.Package, ch *model.Channel, version, replaces string,
245253
getCSVJson(pkg.Name, version),
246254
getCRDJSON(),
247255
},
248-
Version: semver.MustParse(version),
256+
Version: bsemver.MustParse(version),
249257
}
250258
}
251259

alpha/model/model.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ type Bundle struct {
338338
Package *Package
339339
Channel *Channel
340340
Name string
341+
Version semver.Version
341342
Image string
342343
Replaces string
343344
Skips []string
@@ -354,7 +355,6 @@ type Bundle struct {
354355

355356
// These fields are used to compare bundles in a diff.
356357
PropertiesP *property.Properties
357-
Version semver.Version
358358
}
359359

360360
func (b *Bundle) Validate() error {
@@ -391,8 +391,20 @@ func (b *Bundle) Validate() error {
391391
// }
392392
//}
393393

394-
if props != nil && len(props.Packages) != 1 {
395-
result.subErrors = append(result.subErrors, fmt.Errorf("must be exactly one property with type %q", property.TypePackage))
394+
if b.Version.String() == "0.0.0" {
395+
result.subErrors = append(result.subErrors, errors.New("version must be set"))
396+
}
397+
398+
if props != nil {
399+
if len(props.Packages) > 1 {
400+
result.subErrors = append(result.subErrors, errors.New("no more than one olm.package property can be defined"))
401+
}
402+
if len(props.Packages) == 1 {
403+
pkgProp := props.Packages[0]
404+
if pkgProp.PackageName != b.Package.Name || pkgProp.Version != b.Version.String() {
405+
result.subErrors = append(result.subErrors, errors.New("olm.package property does not match bundle's package name and version"))
406+
}
407+
}
396408
}
397409

398410
if b.Image == "" && len(b.Objects) == 0 {

alpha/model/model_test.go

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ func TestValidators(t *testing.T) {
466466
Package: pkg,
467467
Channel: ch,
468468
Name: "anakin.v0.1.0",
469+
Version: semver.MustParse("0.1.0"),
469470
Image: "registry.io/image",
470471
Replaces: "anakin.v0.0.1",
471472
Skips: []string{"anakin.v0.0.2"},
@@ -476,12 +477,29 @@ func TestValidators(t *testing.T) {
476477
},
477478
assertion: require.NoError,
478479
},
480+
{
481+
name: "Bundle/Success/NoPackageProperty",
482+
v: &Bundle{
483+
Package: pkg,
484+
Channel: ch,
485+
Name: "anakin.v0.1.0",
486+
Version: semver.MustParse("0.1.0"),
487+
Image: "registry.io/image",
488+
Replaces: "anakin.v0.0.1",
489+
Skips: []string{"anakin.v0.0.2"},
490+
Properties: []property.Property{
491+
property.MustBuildGVK("skywalker.me", "v1alpha1", "PodRacer"),
492+
},
493+
},
494+
assertion: require.NoError,
495+
},
479496
{
480497
name: "Bundle/Success/ReplacesNotInChannel",
481498
v: &Bundle{
482499
Package: pkg,
483500
Channel: ch,
484501
Name: "anakin.v0.1.0",
502+
Version: semver.MustParse("0.1.0"),
485503
Image: "registry.io/image",
486504
Replaces: "anakin.v0.0.0",
487505
Properties: []property.Property{
@@ -496,6 +514,7 @@ func TestValidators(t *testing.T) {
496514
Package: pkg,
497515
Channel: ch,
498516
Name: "anakin.v0.1.0",
517+
Version: semver.MustParse("0.1.0"),
499518
Image: "",
500519
Properties: []property.Property{
501520
property.MustBuildPackage("anakin", "0.1.0"),
@@ -513,6 +532,7 @@ func TestValidators(t *testing.T) {
513532
Package: pkg,
514533
Channel: ch,
515534
Name: "anakin.v0.1.0",
535+
Version: semver.MustParse("0.1.0"),
516536
Image: "",
517537
Properties: []property.Property{
518538
property.MustBuildPackage("anakin", "0.1.0"),
@@ -574,24 +594,56 @@ func TestValidators(t *testing.T) {
574594
assertion: hasError(`skip[0] is empty`),
575595
},
576596
{
577-
name: "Bundle/Error/MissingPackage",
597+
name: "Bundle/Error/MissingVersion",
578598
v: &Bundle{
579-
Package: pkg,
580-
Channel: ch,
581-
Name: "anakin.v0.1.0",
582-
Image: "",
583-
Replaces: "anakin.v0.0.1",
584-
Skips: []string{"anakin.v0.0.2"},
585-
Properties: []property.Property{},
599+
Package: pkg,
600+
Channel: ch,
601+
Name: "anakin.v0.1.0",
602+
Image: "",
603+
Replaces: "anakin.v0.0.1",
604+
Skips: []string{"anakin.v0.0.2"},
605+
},
606+
assertion: hasError(`version must be set`),
607+
},
608+
{
609+
name: "Bundle/Error/PackagePropertyMismatchedPackageName",
610+
v: &Bundle{
611+
Package: pkg,
612+
Channel: ch,
613+
Name: "anakin.v0.1.0",
614+
Version: semver.MustParse("0.1.0"),
615+
Image: "",
616+
Replaces: "anakin.v0.0.1",
617+
Skips: []string{"anakin.v0.0.2"},
618+
Properties: []property.Property{
619+
property.MustBuildPackage("vader", "0.1.0"),
620+
},
621+
},
622+
assertion: hasError(`olm.package property does not match bundle's package name and version`),
623+
},
624+
{
625+
name: "Bundle/Error/PackagePropertyMismatchedVersion",
626+
v: &Bundle{
627+
Package: pkg,
628+
Channel: ch,
629+
Name: "anakin.v0.1.0",
630+
Version: semver.MustParse("0.1.0"),
631+
Image: "",
632+
Replaces: "anakin.v0.0.1",
633+
Skips: []string{"anakin.v0.0.2"},
634+
Properties: []property.Property{
635+
property.MustBuildPackage("anakin", "0.2.0"),
636+
},
586637
},
587-
assertion: hasError(`must be exactly one property with type "olm.package"`),
638+
assertion: hasError(`olm.package property does not match bundle's package name and version`),
588639
},
589640
{
590641
name: "Bundle/Error/MultiplePackages",
591642
v: &Bundle{
592643
Package: pkg,
593644
Channel: ch,
594645
Name: "anakin.v0.1.0",
646+
Version: semver.MustParse("0.1.0"),
595647
Image: "",
596648
Replaces: "anakin.v0.0.1",
597649
Skips: []string{"anakin.v0.0.2"},
@@ -600,7 +652,7 @@ func TestValidators(t *testing.T) {
600652
property.MustBuildPackage("anakin", "0.2.0"),
601653
},
602654
},
603-
assertion: hasError(`must be exactly one property with type "olm.package"`),
655+
assertion: hasError(`no more than one olm.package property can be defined`),
604656
},
605657
{
606658
name: "RelatedImage/Success/Valid",

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.3
55
toolchain go1.23.6
66

77
require (
8+
github.com/Masterminds/semver/v3 v3.3.1
89
github.com/akrylysov/pogreb v0.10.2
910
github.com/blang/semver/v4 v4.0.0
1011
github.com/containerd/containerd v1.7.27

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
1414
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
1515
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
1616
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
17+
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
18+
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
1719
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
1820
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
1921
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=

0 commit comments

Comments
 (0)