diff --git a/alpha/action/render.go b/alpha/action/render.go index f6ed285b4..98aca0c03 100644 --- a/alpha/action/render.go +++ b/alpha/action/render.go @@ -450,10 +450,7 @@ BundleLoop: func combineConfigs(cfgs []declcfg.DeclarativeConfig) *declcfg.DeclarativeConfig { out := &declcfg.DeclarativeConfig{} for _, in := range cfgs { - out.Packages = append(out.Packages, in.Packages...) - out.Channels = append(out.Channels, in.Channels...) - out.Bundles = append(out.Bundles, in.Bundles...) - out.Others = append(out.Others, in.Others...) + out.Merge(&in) } return out } diff --git a/alpha/declcfg/declcfg.go b/alpha/declcfg/declcfg.go index f688574b1..0a62aae0e 100644 --- a/alpha/declcfg/declcfg.go +++ b/alpha/declcfg/declcfg.go @@ -16,16 +16,18 @@ import ( ) const ( - SchemaPackage = "olm.package" - SchemaChannel = "olm.channel" - SchemaBundle = "olm.bundle" + SchemaPackage = "olm.package" + SchemaChannel = "olm.channel" + SchemaBundle = "olm.bundle" + SchemaDeprecation = "olm.catalog.deprecation" ) type DeclarativeConfig struct { - Packages []Package - Channels []Channel - Bundles []Bundle - Others []Meta + Packages []Package + Channels []Channel + Bundles []Bundle + Deprecations []Deprecation + Others []Meta } type Package struct { @@ -90,6 +92,19 @@ type RelatedImage struct { Image string `json:"image"` } +type Deprecation struct { + Schema string `json:"schema"` + Package string `json:"package"` + Name string `json:"name,omitempty"` + Deprecations []DeprecationEntry `json:"deprecations"` +} + +type DeprecationEntry struct { + Schema string `json:"schema"` + Name string `json:"name,omitempty"` + Message json.RawMessage `json:"message"` +} + type Meta struct { Schema string Package string @@ -181,3 +196,11 @@ func extractUniqueMetaKeys(blobMap map[string]any, m *Meta) error { } return nil } + +func (destination *DeclarativeConfig) Merge(src *DeclarativeConfig) { + destination.Packages = append(destination.Packages, src.Packages...) + destination.Channels = append(destination.Channels, src.Channels...) + destination.Bundles = append(destination.Bundles, src.Bundles...) + destination.Others = append(destination.Others, src.Others...) + destination.Deprecations = append(destination.Deprecations, src.Deprecations...) +} diff --git a/alpha/declcfg/load.go b/alpha/declcfg/load.go index 40528d902..a493b2204 100644 --- a/alpha/declcfg/load.go +++ b/alpha/declcfg/load.go @@ -227,12 +227,8 @@ func mergeCfgs(ctx context.Context, cfgChan <-chan *DeclarativeConfig, fcfg *Dec if !ok { return nil } - fcfg.Packages = append(fcfg.Packages, cfg.Packages...) - fcfg.Channels = append(fcfg.Channels, cfg.Channels...) - fcfg.Bundles = append(fcfg.Bundles, cfg.Bundles...) - fcfg.Others = append(fcfg.Others, cfg.Others...) + fcfg.Merge(cfg) } - } } @@ -297,6 +293,12 @@ func LoadReader(r io.Reader) (*DeclarativeConfig, error) { return fmt.Errorf("parse bundle: %v", err) } cfg.Bundles = append(cfg.Bundles, b) + case SchemaDeprecation: + var d Deprecation + if err := json.Unmarshal(in.Blob, &d); err != nil { + return fmt.Errorf("parse deprecation: %w", err) + } + cfg.Deprecations = append(cfg.Deprecations, d) case "": return fmt.Errorf("object '%s' is missing root schema field", string(in.Blob)) default: diff --git a/alpha/declcfg/load_test.go b/alpha/declcfg/load_test.go index 828beaf7e..6a1ccd0c5 100644 --- a/alpha/declcfg/load_test.go +++ b/alpha/declcfg/load_test.go @@ -97,13 +97,14 @@ func TestLoadReader(t *testing.T) { func TestWalkMetasFS(t *testing.T) { type spec struct { - name string - fsys fs.FS - assertion require.ErrorAssertionFunc - expectNumPackages int - expectNumChannels int - expectNumBundles int - expectNumOthers int + name string + fsys fs.FS + assertion require.ErrorAssertionFunc + expectNumPackages int + expectNumChannels int + expectNumBundles int + expectNumDeprecations int + expectNumOthers int } specs := []spec{ { @@ -122,19 +123,20 @@ func TestWalkMetasFS(t *testing.T) { assertion: require.Error, }, { - name: "Success/ValidDir", - fsys: validFS, - assertion: require.NoError, - expectNumPackages: 3, - expectNumChannels: 0, - expectNumBundles: 12, - expectNumOthers: 1, + name: "Success/ValidDir", + fsys: validFS, + assertion: require.NoError, + expectNumPackages: 3, + expectNumChannels: 0, + expectNumBundles: 12, + expectNumDeprecations: 1, + expectNumOthers: 1, }, } for _, s := range specs { t.Run(s.name, func(t *testing.T) { - numPackages, numChannels, numBundles, numOthers := 0, 0, 0, 0 + numPackages, numChannels, numBundles, numDeprecations, numOthers := 0, 0, 0, 0, 0 err := WalkMetasFS(s.fsys, func(path string, meta *Meta, err error) error { if err != nil { return err @@ -146,6 +148,8 @@ func TestWalkMetasFS(t *testing.T) { numChannels++ case SchemaBundle: numBundles++ + case SchemaDeprecation: + numDeprecations++ default: numOthers++ } @@ -332,6 +336,18 @@ func TestLoadFS(t *testing.T) { Schema: "olm.bundle", }, }, + Deprecations: []Deprecation{ + { + Schema: "olm.catalog.deprecation", + Package: "kiali", + Name: "bobs-discount-name", + Deprecations: []DeprecationEntry{ + {Schema: "olm.bundle", Name: "kiali-operator.v1.68.0", Message: json.RawMessage(`"kiali-operator.v1.68.0 is deprecated. Uninstall and install kiali-operator.v1.72.0 for support.\n"`)}, + {Schema: "olm.package", Name: "kiali", Message: json.RawMessage(`"package kiali is end of life. Please use 'kiali-new' package for support.\n"`)}, + {Schema: "olm.channel", Name: "alpha", Message: json.RawMessage(`"channel alpha is no longer supported. Please switch to channel 'stable'.\n"`)}, + }, + }, + }, Others: []Meta{ {Schema: "unexpected", Package: "", Blob: json.RawMessage(`{ "schema": "unexpected" }`)}, }, @@ -881,6 +897,25 @@ present in the .indexignore file.`), unrecognizedSchema = &fstest.MapFile{ Data: []byte(`{"schema":"olm.package"}{"schema":"unexpected"}{"schema":"olm.bundle"}`), } + deprecations = &fstest.MapFile{ + Data: []byte(`--- +schema: olm.catalog.deprecation +package: kiali +name: bobs-discount-name +deprecations: +- schema: olm.bundle + name: kiali-operator.v1.68.0 + message: | + kiali-operator.v1.68.0 is deprecated. Uninstall and install kiali-operator.v1.72.0 for support. +- schema: olm.package + name: kiali + message: | + package kiali is end of life. Please use 'kiali-new' package for support. +- schema: olm.channel + name: alpha + message: | + channel alpha is no longer supported. Please switch to channel 'stable'.`), + } validFS = fstest.MapFS{ ".indexignore": indexIgnore, @@ -889,18 +924,19 @@ present in the .indexignore file.`), "etcdoperator.v0.6.1.clusterserviceversion.yaml": etcdCSV, "README.md": readme, "unrecognized-schema.json": unrecognizedSchema, + "deprecations.yaml": deprecations, } ) +type EvaluationFunc func(*testing.T, *DeclarativeConfig) + func TestLoadFile(t *testing.T) { type spec struct { - name string - fsys fs.FS - path string - assertion require.ErrorAssertionFunc - expectNumPackages int - expectNumBundles int - expectNumOthers int + name string + fsys fs.FS + path string + assertion require.ErrorAssertionFunc + expect EvaluationFunc } specs := []spec{ { @@ -944,22 +980,38 @@ func TestLoadFile(t *testing.T) { assertion: require.Error, }, { - name: "Success/UnrecognizedSchema", - fsys: validFS, - path: "unrecognized-schema.json", - assertion: require.NoError, - expectNumPackages: 1, - expectNumBundles: 1, - expectNumOthers: 1, + name: "Success/UnrecognizedSchema", + fsys: validFS, + path: "unrecognized-schema.json", + assertion: require.NoError, + expect: func(t *testing.T, d *DeclarativeConfig) { + require.Equal(t, 1, len(d.Packages)) + require.Equal(t, 1, len(d.Bundles)) + require.Equal(t, 1, len(d.Others)) + }, }, { - name: "Success/ValidFile", - fsys: validFS, - path: "etcd.yaml", - assertion: require.NoError, - expectNumPackages: 1, - expectNumBundles: 6, - expectNumOthers: 0, + name: "Success/ValidFile", + fsys: validFS, + path: "etcd.yaml", + assertion: require.NoError, + expect: func(t *testing.T, d *DeclarativeConfig) { + require.Equal(t, 1, len(d.Packages)) + require.Equal(t, 6, len(d.Bundles)) + require.Equal(t, 0, len(d.Others)) + }, + }, + { + name: "Success/ValidFile/Deprecations", + fsys: validFS, + path: "deprecations.yaml", + assertion: require.NoError, + expect: func(t *testing.T, d *DeclarativeConfig) { + require.Equal(t, 0, len(d.Packages)) + require.Equal(t, 0, len(d.Bundles)) + require.Equal(t, 0, len(d.Others)) + require.Equal(t, 1, len(d.Deprecations)) + }, }, } @@ -969,9 +1021,7 @@ func TestLoadFile(t *testing.T) { s.assertion(t, err) if err == nil { require.NotNil(t, cfg) - assert.Equal(t, len(cfg.Packages), s.expectNumPackages, "unexpected package count") - assert.Equal(t, len(cfg.Bundles), s.expectNumBundles, "unexpected bundle count") - assert.Equal(t, len(cfg.Others), s.expectNumOthers, "unexpected others count") + s.expect(t, cfg) } }) } diff --git a/alpha/declcfg/write.go b/alpha/declcfg/write.go index 2f149e5af..41a731310 100644 --- a/alpha/declcfg/write.go +++ b/alpha/declcfg/write.go @@ -377,6 +377,12 @@ func writeToEncoder(cfg DeclarativeConfig, enc encoder) error { pkgNames.Insert(pkgName) othersByPackage[pkgName] = append(othersByPackage[pkgName], o) } + deprecationsByPackage := map[string][]Deprecation{} + for _, d := range cfg.Deprecations { + pkgName := d.Package + pkgNames.Insert(pkgName) + deprecationsByPackage[pkgName] = append(deprecationsByPackage[pkgName], d) + } for _, pName := range pkgNames.List() { if len(pName) == 0 { @@ -418,6 +424,16 @@ func writeToEncoder(cfg DeclarativeConfig, enc encoder) error { return err } } + + deprecations := deprecationsByPackage[pName] + sort.SliceStable(deprecations, func(i, j int) bool { + return deprecations[i].Name < deprecations[j].Name + }) + for _, d := range deprecations { + if err := enc.Encode(d); err != nil { + return err + } + } } for _, o := range othersByPackage[""] { @@ -425,6 +441,7 @@ func writeToEncoder(cfg DeclarativeConfig, enc encoder) error { return err } } + return nil } diff --git a/alpha/template/semver/semver.go b/alpha/template/semver/semver.go index 2445efb3d..d42afedf3 100644 --- a/alpha/template/semver/semver.go +++ b/alpha/template/semver/semver.go @@ -387,10 +387,7 @@ func newChannel(pkgName string, chName string) *declcfg.Channel { func combineConfigs(cfgs []declcfg.DeclarativeConfig) *declcfg.DeclarativeConfig { out := &declcfg.DeclarativeConfig{} for _, in := range cfgs { - out.Packages = append(out.Packages, in.Packages...) - out.Channels = append(out.Channels, in.Channels...) - out.Bundles = append(out.Bundles, in.Bundles...) - out.Others = append(out.Others, in.Others...) + out.Merge(&in) } return out }