--- tags: fbc --- # Deprecating channels This document investigates methods of including channel deprecation metadata in file-based catalogs (FBCs) and the potential side effects of the different approaches. You can track the progress of this investigation on the OLM Jira board: https://issues.redhat.com/browse/OLM-2707. This is meant only for FBC based indices, not for SQLite. ## Background ### Why support Channel Deprecation? The catalogs used by OLM, whether FBC or SQLite based, have a set of operator packages, each with one or more upgrade paths defined between the operator bundle versions it references. A user on-cluster can create a `Subscription` to a specific channel on a package to automatically upgrade their operator installation for any new version released on that channel. OLM does not impose channel structure or naming conventions within a package. While this allows operator authors to have unlimited flexibility, the resulting variability between channel structuring in packages makes maintaining their FBC almost entirely manual. Veneers simplify FBC maintenance, but more complex veneers like the semver veneer require the adoption of a standard naming scheme within channels to be useful. The lack of standardisation also provides for a poor user experience. Currently for EUS to EUS upgrade, users have to move to a separate channel to get upgrades. The adoption of the semver veneer would also address this, but requires a way for operator authors to migrate their old channel structure to one that conforms with a standard naming scheme. Since FBC has no concept of channel aliases, this requires removal of old channels once the new ones are in place. Removing channels without warning however, can leave a cluster admin with one operator on a broken channel - breaking resolution and the console interface for channel selection. This would then require inspecting all subscriptions in the broken namespace and updating the faulty one manually. This can partly be addressed by having subscriptions with unkown channels fail independenty of resolution, OLM would also not provide any upgrades for the bad subscription. Channel deprecation is a means to warn users about the future removal of a channel, giving ample time for cluster admins to move off a deprecated channel. Unlike bundle deprecation which was introduced to block installation while still enabling upgrade from deprecated versions, channel deprecation should not stop users from using the deprecated channel: It is meant as a warning. For this warning to be meaningful, both users trying to install from a deprecated channel and those already to a channel that becomes deprecated must be informed. A deprecated channel should also be able to provide additional information, like alternative channels a user could switch to. ### Channel removal today Removing a channel requires enormous effort in SQLite catalogs as channel information is carried within the bundles themselves: each bundle would have to first be rebuilt to exclude the channel, the index to modify pruned of the package with the removed bundle and then add back all the rebuilt bundles to the catalog. In contrast, this can be done in FBC by removing a single channel object from the catalog's json or yaml. Deprecation is hence discussed in terms of FBC only. If a channel with an existing subscription is removed from the catalog, this will immediately be noticable in the form of resolution errors like below: ``` 'constraints not satisfiable: no operators found in channel deprecated-channel of package example-op in the catalog referenced by subscription example-sub, subscription example-sub exists' ``` This does not affect the function of already installed operators. Since resolution is namespace scoped, all operators in that namespace will not resolve till the subscription is patched or deleted. If an alternate channel is known, the subscription can be switched over to the other channel to resolve the issue. ### About Channels A channel is an ordered upgrade path through bundle versions that an installed bundle can follow. The same bundle can be present on different channels, and for File Based Catalogs, bundles can have different upgrade edges based on different channels. In SQLite indexes, channels are stored as sets of (`package name`, `channel name`, `bundle name`, [`replaces`]). This representation does not allow any channel-level properties. The only additional information stored about channels is the channel head and default channel name. With the introduction of FBC, channels became separate objects, capable of having properties: ```yaml schema: olm.channel package: <package name> name: <channel name> entries: - name: <bundle name> replaces: <replaces bundle name> skips: [<skipped bundle list>] skipRange: <skipRange semver> - ... properties: - type: <type string> value: <value json> - ... ``` To keep backward compatibility to SQLite indices, OLM and related components don't use channel-level metadata. The way these components interact with channels is shown below: ```mermaid graph LR subgraph discovery package-server --> console package-server --> k[oc/kubectl] end Index -->|bundle list| package-server console --> Subscription --> Resolver Index -->|bundle list| Resolver subgraph resolution csvs[Installed CSVs] --> Resolver --> ResolveSet end ``` #### Channel discovery If a user knows the exact name of the package they want to install, they can create a subscription to install the version of that operator at the default channel head. Installing any other CSV version or subscribing to any other channel requires knowing the exact name of that CSV or channel. ```yaml apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: <subscription name> namespace: <subscription namespace> spec: source: <catalogSource to install from> sourceNamespace: <catalogSource namespace> name: <package name> startingCSV: <specific bundle name to install> #if unspecified, installs channel head channel: <channel for upgrades> #if unspecified, uses default channel ... status: ... ``` GRPC calls to an index can allow a user to figure out all the packages provided by an index along with what channels each package supports and what their head versions are: This is the role of the OLM `package-server`. It stores this in the form of `PackageManifest` resources on the cluster, which it updates whenever there is a change in a `catalogSource` ```yaml apiVersion: packages.operators.coreos.com/v1 kind: PackageManifest metadata: labels: <catalogSource, os/arch, CSV labels> name: <package name> namespace: <namespace> spec: {} status: packageName: <package name> defaultChannel: <default channel name> channels: - currentCSV: <CSV name> currentCSVDesc: annotations: [<annotations list from CSV>] apiservicedefinitions: {...} customresourcedefinitions: {...} displayName: <displayNames> installModes: [<supported installModes>] keywords: [<search keywords>] links: [<external links>] maintainers: [<maintainers list>] maturity: <maturity level> provider: name: <provider name> relatedImages: [<relatedImages>] version: <version> name: <channel name> - ... ``` A PackageManifest can provide an overview of all channels along with key fields in their head CSVs. The PackageManifest cache is constructed using a set of GRPC calls to the registry, as detailed below: ```mermaid graph TD A(( )) --- ListPackages --> PackageName1 ListPackages --> PackageName2 ListPackages --> B[...] PackageName1 --- GetPackage GetPackage --> PackageName subgraph HeadCSV CSVName CSVJSON end subgraph package PackageName DefaultChannel subgraph channel ChannelName1 ChannelName2 C[...] end end ChannelName1 --> D["GetBundleForChannel (deprecated)"] --> CSVName subgraph PackageManifest pname[packageName] pdefault[defaultChannel] subgraph pHead[channels] pChan[name] pCSV[currentCSV] pJSON[currentCSVDesc] end end PackageName --> pname DefaultChannel --> pdefault ChannelName1 --> pChan CSVName --> pCSV CSVJSON --> pJSON classDef grpc fill:#fff,stroke-width:0px; class ListPackages,GetPackage,D grpc ``` In addition to being accessible via the oc/kubectl, the OpenShift Console also uses PackageManifests for listing the valid channels for installation. <!--Console exclusively uses package manifests https://github.com/openshift/console/blob/2ad4e17d76acbe72171407fc1c66ca4596c8aac4/frontend/packages/operator-lifecycle-manager/src/components/index.tsx --> #### Channels in resolution Resolution uses a different intermediary store, with a resolver cache of simplified Entries. This cache is generated with the `ListBundles` GRPC call which generates a list of `Bundle` objects which are then transformed into `Entry` Objects. ```go type Bundle struct { CsvName string PackageName string ChannelName string CsvJson string Object []string BundlePath string ProvidedApis []*GroupVersionKind RequiredApis []*GroupVersionKind Version string SkipRange string Dependencies []*Dependency Properties []*Property Replaces string Skips []string } ``` ```go type Entry struct { Name string Replaces string Skips []string SkipRange semver.Range ProvidedAPIs APISet RequiredAPIs APISet Version *semver.Version SourceInfo struct { Package string Channel string StartingCSV string Catalog SourceKey DefaultChannel bool Subscription *v1alpha1.Subscription } Properties []*api.Property //Bundle Properties + legacy dependencies + provided/required APIs BundlePath string Bundle *api.Bundle } ``` The resolver uses these Entries along with Subscriptions to resolve what the set of installed operators should be. The `ListBundles` call used in resolution behaves identically for FBC and SQLite indices. #### Channel Deprecation for new operators ##### Required changes in FBC: ##### 1. channel-level properties One way to mark a channel as deprecated would be at the channel level, as a channel level property on FBC. ```yaml schema: olm.channel package: my-pkg name: deprecated-channel entries: - name: bundle-1 replaces: bundle-2 - name: bundle-2 properties: - type: olm.deprecated.channel value: '{"name":"deprecated-channel","message": "custom optional warning to log", "fallback":["channel-1","channel-2"]}' ``` A new GRPC endpoint, like a `GetChannelsForPackage` or `ListPackageChannels` could then be used to provide channel-level metadata in a form similar to the `struct` described below. ```go type PackageChannel struct { Package string Properties []*Property // package-level properties Channels []*struct{ { Channel string Properties []*Property // channel-level properties ChannelHead string ChannelHeadJSON string ... } } } ``` To avoid adding another GRPC call, the `Bundle` object returned by both `ListBundles` and `GetBundleForChannel` GRPC calls can be modified to support channel properties, either in a new field or by merging them with bundle properties ```go type Bundle struct { CsvName string PackageName string ChannelName string CsvJson string Object []string BundlePath string ProvidedApis []*GroupVersionKind RequiredApis []*GroupVersionKind Version string SkipRange string Dependencies []*Dependency Properties []*Property // Bundle+channel properties Replaces string Skips []string } ``` The channel-level properties could also be present on bundles from the start, but a single bundle can belong to a mix of deprecated and non-deprecated channels. Adding channel deprecation to every bundle of a deprecated channel will increase the index size while also being ambiguous unless the channel name being looked at is specified elsewhere, either from another GRPC call or from the subscription. It is also possible to have the channel deprecation property present only on the head of a deprecated channel and achieve the same result without as much of an increase in index size. ##### Changes required for Console The deprecation information provided by the FBC will need to be understood by console. This can then be used to either hide deprecated channels from new users or to warn them about selecting deprecated channels. ###### Modified package-server API Once the deprecation property is on the FBC, it has to be made available to the console. Currently, the interface between the two is via the package-server, through the `PackageManifest` API it maintains. One way to do this with no modifications to the current API whatsoever is by adding an annotation for tracking deprecated channels: ```yaml annotations: "olm.deprecated.channel/deprecated-channel-name": '{"message": "Channel x will be deprecated and removed on dd-mm-yyyy, see fallback options for other channels you can switch to. See our announcement at operator-site.org/blog/please-dont-make-us-support-tegacy-code", "fallback":["channel-1","channel-2"]}' ``` This comes with a 253-character kube annotation length limit, which is incidentally broken by the above example. The `PackageManifest` API can also have a new field to reflect the deprecation. ```yaml apiVersion: packages.operators.coreos.com/v1alpha1 kind: PackageManifest metadata: labels: <catalogSource, os/arch, CSV labels> name: my-pkg namespace: my-ns spec: {} status: packageName: my-pkg defaultChannel: channel-2 channels: - currentCSV: bundle-1 currentCSVDesc: ... deprecated: message: "" fallback: - channel-1 - channel-2 name: deprecated-channel - currentCSV: bundle-1 currentCSVDesc: ... name: channel-1 ``` Alternatively, the entire PackageManifest API can be deprecated in favor of a new FBC-centric api. Such an API might look similar to the sample below. ```yaml apiVersion: packages.operators.coreos.com/v2alpha1 kind: PackageManifest metadata: creationTimestamp: "YYYY-MM-DDTHH:MM:SSZ" labels: catalog: <catalogSource name> catalog-namespace: <catalogSource namespace> operatorframework.io/arch.amd64: supported operatorframework.io/os.linux: supported provider: <provider> provider-url: "" name: <package name> namespace: <namespace> spec: {} status: catalogSource: name: <catalogSource name> displayName: <catalogSource display name> namespace: <catalogSource namespace> publisher: <catalogSource publisher> properties: [] package: name: <package name> defaultChannel: <default channel> icon: <operator icon> properties: - provider: <name>,<url> - maintainers: [<name, url>] - description: <package description> - certified: <bool, whether package is certified> - categories: <search categories> - keywords: <search keywords> - capabilities: <operator capabilities> channels: - name: <channel name> properties: - deprecated: true - <overrides for any package level properties, channel level properties> currentCSV: name: <currentCSV name> version: <CSV version> properties: - createdAt: "YYYY-MM-DDTHH:MM:SSZ" - installModes: <supported install modes> - <overrides for any package/channel level properties, bundle level properties> desc: apiservicedefinitions: owned: - kind: <apiservice kind> name: <apiservice name> version: <apiservice version> description: <apiservice description> displayName: <apiservice display name> required: - kind: <apiservice kind> name: <apiservice name> version: <apiservice version> description: <apiservice description> displayName: <apiservice display name> customresourcedefinitions: owned: - kind: <crd kind> name: <crd name> version: <crd version> description: <crd description> displayName: <crd display name> required: - kind: <crd kind> name: <crd name> version: <crd version> description: <crd description> displayName: <crd display name> ``` ###### FBC Direct Interface The modified API presented earlier is a stripped down version of FBC. A direct FBC interface is a simpler solution to this. An interface that allows reading only a specific set of packages can easily take the role of a `PackageManifest`. A single operator's FBC might look like: ```yaml --- defaultChannel: kiali-0.46.x name: kiali-operator schema: olm.package icon: base64data: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIyLjAuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTk1IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTk1OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzE5MkM0Nzt9Cgkuc3Qxe2ZpbGw6dXJsKCNTVkdJRF8xXyk7fQoJLnN0MntmaWxsOnVybCgjU1ZHSURfMl8pO30KCS5zdDN7ZmlsbDp1cmwoI1NWR0lEXzNfKTt9Cgkuc3Q0e2ZpbGw6dXJsKCNTVkdJRF80Xyk7fQoJLnN0NXtmaWxsOnVybCgjU1ZHSURfNV8pO30KCS5zdDZ7ZmlsbDp1cmwoI1NWR0lEXzZfKTt9Cjwvc3R5bGU+CjxnPgoJPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIyNTYsNSAxLDE1Mi4yIDEsNDQ2LjcgMjU2LDU5My45IDUxMSw0NDYuNyA1MTEsMTUyLjIgMjU2LDUgCSIvPgoJPGxpbmVhckdyYWRpZW50IGlkPSJTVkdJRF8xXyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxMDkuNDk5NiIgeTE9Ijg0Ljk2MjIiIHgyPSI0NDAuOTUxNyIgeTI9Ijc5My44MTAyIj4KCQk8c3RvcCAgb2Zmc2V0PSIwIiBzdHlsZT0ic3RvcC1jb2xvcjojRkZGRkZGIi8+CgkJPHN0b3AgIG9mZnNldD0iMSIgc3R5bGU9InN0b3AtY29sb3I6IzU0QkFEOCIvPgoJPC9saW5lYXJHcmFkaWVudD4KCTxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0yNTYsNTYxbDIyNi41LTEzMC44di0yNi4zYy0zNy42LTcuMy04NC45LTE0LjMtMTQzLjUtMTkuM2MtMTk5LjItMTcuMi0xNC44LTU2LjYsNjguOS0xMjcuMQoJCVMxMzAsMTY1LjcsMTMwLDE2NS43czE0Ny42LDMxLDEzMi44LDUwYy0xMC41LDEzLjUtMTM0LjMsNDMuNS0yMzMuMyw4OC4xdjEyNi41TDI1Niw1NjF6Ii8+CjwvZz4KPC9zdmc+Cg== mediatype: image/svg+xml --- entries: - name: kiali-cluster-operator.v1.43.0 - name: kiali-cluster-operator.v1.44.1 replaces: kiali-cluster-operator.v1.43.0 properties: type: olm.deprecated.channel value: '{"fallback":["kiali-0.46.x"]}' name: kiali-0.44.x package: kiali-operator schema: olm.channel --- entries: - name: kiali-cluster-operator.v1.43.0 - name: kiali-cluster-operator.v1.44.1 replaces: kiali-cluster-operator.v1.43.0 - name: kiali-cluster-operator.v1.45.0 replaces: kiali-cluster-operator.v1.44.1 - name: kiali-cluster-operator.v1.46.0 replaces: kiali-cluster-operator.v1.45.0 name: kiali-0.46.x package: kiali-operator schema: olm.channel --- image: quay.io/operatorhubio/kiali:v1.43.0 name: kiali-cluster-operator.v1.43.0 package: kiali-operator properties: - type: olm.gvk value: group: kiali.io kind: Kiali version: v1alpha1 - type: olm.package value: packageName: kiali-operator version: 1.43.0 relatedImages: - image: quay.io/kiali/kiali-operator:v1.43.0 name: "" - image: quay.io/operatorhubio/kiali:v1.43.0 name: "" schema: olm.bundle --- image: quay.io/ankitathomas/operator:failing-csv name: kiali-cluster-operator.v1.44.1 package: kiali-operator properties: - type: olm.gvk value: group: kiali.io kind: Kiali version: v1alpha1 - type: olm.package value: packageName: kiali-operator version: 1.44.1 relatedImages: - image: quay.io/kiali/kiali-operator:v1.44.1 name: "" - image: quay.io/operatorhubio/kiali:v1.44.1 name: "" schema: olm.bundle --- image: quay.io/operatorhubio/kiali:v1.45.0 name: kiali-cluster-operator.v1.45.0 package: kiali-operator properties: - type: olm.gvk value: group: kiali.io kind: Kiali version: v1alpha1 - type: olm.package value: packageName: kiali-operator version: 1.45.0 relatedImages: - image: quay.io/kiali/kiali-operator:v1.45.0 name: "" - image: quay.io/operatorhubio/kiali:v1.45.0 name: "" schema: olm.bundle --- image: quay.io/operatorhubio/kiali:v1.46.0 name: kiali-cluster-operator.v1.46.0 package: kiali-operator properties: - type: olm.gvk value: group: kiali.io kind: Kiali version: v1alpha1 - type: olm.package value: packageName: kiali-operator version: 1.46.0 - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY... - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3Rlc... relatedImages: - image: quay.io/kiali/kiali-operator:v1.46.0 name: "" - image: quay.io/operatorhubio/kiali:v1.46.0 name: "" schema: olm.bundle ``` #### Channel Deprecation for installed operators Once channel deprecation information is available, olm must also surface a warning to already installed bundles. The easiest place to do so would be on the subscription itself. It could also be emitted as an event or just logged in the olm-operator while reconciling subscriptions. ```mermaid graph LR subgraph subscription channel status end OLM -->|GetBundleForChannel| registry -->|deprecated channel list| OLM channel --> OLM -->|deprecation condition| status OLM -->|deprecation warning| event ``` ### Other questions: - How do we handle deprecated dependencies? - One option is to have an `allow-deprecation` constraint on only the top-level bundles and to disregard deprecated channels for dependencies. An alternative is to also surface deprecated dependencies in subscriptions, but since resolution is per namespace and not per subscription, this isn't viable. Events are a good way to show deprecated dependencies, but this again does not give information about what operator requires the deprecated dependency. Operator developers will need to be careful to track deprecation of their dependencies and diligent in informing their users when their own operator is deprecated - What if an entire package is deprecated? - This is especially important for dependencies: if all deprecated channels are blocked for dependencies, this may lead to installs failing where they don't have to. If all deprecated channels are allowed, operators may continue installing dependency versions that exist only on deprecated channels and could face issues later when those channels (and bundles within) are removed. - What considerations do we need to make when deprecating a default channel? - a default channel should be no different from any other channel and is mainly useful when a subscription does not specify a channel to upgrade from. This may lead to some confusion where a channel deprecation warning is present on a subscription that has no channel specified, but no major problems overall. The console may need to add special exemptions for the default channel if hiding deprecated channels since the default channel is typically where a user would install an operator from. - Why not extend bundle deprecation to apply to a whole channel? - Bundle deprecation allows users on existing deprecated bundles to upgrade from the bundle while preventing new users from installing a deprecated bundle. This can be extended to add a message about deprecation, but presently, OLM does not allow upgrading between deprecated bundles. Changing the bundle deprecation behavior may lead to unforeseen consequences. - Is channel deprecation reversible? - Unlike bundle deprecation in SQLite indices, channel deprecation does not remove information from the channel. Depending on how the channel deprecation is implemented, it may be as simple as removing a property from an FBC channel or status from a subscription. Changing FBC will likely still require a new index to be released, but that doesn't stop rolling back a deprecation notice. See also: https://github.com/operator-framework/enhancements/pull/113/