---
# System prepended metadata

title: Reasoning around deprecation/delegation in FBC upgrade graphs
tags: [fbc]

---

# Reasoning around deprecation/delegation in FBC upgrade graphs

Prior Art
[Deprecated Channels proposal](https://hackmd.io/xgPy6mErQ6WFoPVkLRdDfA?view) (v0, abandoned)
[PM's Deprecation Management](https://docs.google.com/document/d/1aDY93Vg-jnUvWqSeT5JXoV2272WgB0HaLTjMjmewhBo/)

## Deprecation

### arguments for a separate schema
1. comprehensibility, as a collection of standalone things it makes it easier to reason about them than sprinkling them amongst the graph of bundle+version relationship
2. preserves immutability of existing graphs; no author needs re-release their graph info to update, which can be important for;
3. [ref](https://redhat-internal.slack.com/archives/C3VS0LV41/p1697466208164929) deprecation announcements will likely be orthogonal to release schedules (internal ref to [current deprecation process](https://docs.engineering.redhat.com/display/CFC/Delivery#Delivery-deprecating-bundles), which is a removal process, not true deprecation)

### example schemas for deprecation

#### singleton schema
```yaml=

schema: olm.catalog.meta
deprecations:
- ref: foobar-v0.7
  kind: bundle    #scope indication#
  message: |
     v0.7 is deprecated. Uninstall and install new v0.9 for support.
- ref: foo
  kind: package
  message: |
     package foo is end of life.  Please use 'baz' package for support.
- ref: stable-v0 #some unique string#
  kind: channel #scope indication#
  message: |
     channel stable-v0 is no longer supported.  Please switch to channel 'stable-v1'.
```

#### multiple schemas
```yaml=
schema: olm.deprecation.package
ref: foo
message: |
  package foo is end of life.  Please use 'bar' package for support.
---
schema: olm.deprecation.channel
ref: stable-v0
message: |
  channel stable-v0 is no longer supported.  Please switch to channel 'stable-v1'.
---
schema: olm.deprecation.bundle
ref: foobar-v0.7
message: |
  v0.7 is deprecated.  Uninstall and install new v0.9 for support.

```

#### singleton, satisfying Meta schema

```yaml=
schema: olm.catalog.meta
properties:
- type: olm.deprecation
  value:
    scope: bundle
    ref: foobar-v0.7
    message: |
        v0.7 is deprecated. Uninstall and install new v0.9 for support.
- type: olm.deprecation
  value:
    scope: package
    ref: foo
    message: |
        package foo is end of life.  Please use 'baz' package for support.
- type: olm.deprecation
  value:
    scope: channel
    ref: stable-v0
    message: |
        channel stable-v0 is no longer supported.  Please switch to channel 'stable-v1'.

```

#### multi, satisfying Meta schema

```yaml=
schema: olm.deprecation.bundle
properties:
- type: olm.deprecation.bundle.property
  value:
    ref: foobar-v0.7
    message: |
        v0.7 is deprecated. Uninstall and install new v0.9 for support.
---
schema: olm.deprecation.package
properties:
- type: olm.deprecation.package.property
  value:
    ref: foo
    message: |
        package foo is end of life.  Please use 'baz' package for support.
---
schema: olm.deprecation.channel
properties:
- type: olm.deprecation
  value:
    ref: stable-v0
    message: |
        channel stable-v0 is no longer supported.  Please switch to channel 'stable-v1'.

```

#### singleton, Meta-satisfying, focused-scope schema:

<h5 style="text-align: center;color: red">winnah</h5>

1. requires package to be valid
2. can be 0..1 for each package to be valid
3. name is optional, and not validated


```yaml=
schema: olm.catalog.deprecations
package: foobar
name: ###if desired###
deprecations:
- schema: olm.bundle
  name: foobar-v0.7
  message: |
     foobar-v0.7 is deprecated. Uninstall and install foobar-v0.9 for support.
- schema: olm.package
  message: |
     package foobar is end of life.  Please use 'baz' package for support.
- schema: olm.channel
  name: stable-v0
  message: |
     channel stable-v0 is no longer supported.  Please switch to channel 'stable-v1'.
```

### example illustration of `opm alpha render-graph` output, with deprecated package, channel, and bundle.
note:  package/channel styling doesn't appear to be supported in hackmd but here's what it should look like:![](https://hackmd.io/_uploads/BJTJgrol6.png)


```mermaid
graph LR
   classDef deprecated fill:#f9f
  %% package "rhacs-operator"
  subgraph "rhacs-operator"
    %% channel "stable"
    subgraph rhacs-operator-stable["stable"]
      rhacs-operator-stable-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]
      rhacs-operator-stable-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- replace --> rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]
      rhacs-operator-stable-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.1.0)" --> rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]
      rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- replace --> rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- skip --> rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-stable-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.1.1)" --> rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- "skipRange(>= 4.0.0 < 4.1.1)" --> rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]-- replace --> rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- skip --> rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-stable-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.1.2)" --> rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- "skipRange(>= 4.0.0 < 4.1.2)" --> rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]-- "skipRange(>= 4.0.0 < 4.1.2)" --> rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-stable-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]:::deprecated
      rhacs-operator-stable-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-stable-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-stable-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-stable-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-stable-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-stable-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-stable-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-stable-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]-- replace --> rhacs-operator-stable-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
    end
        %% channel "candidate"
    subgraph rhacs-operator-candidate["candidate"]
      rhacs-operator-candidate-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]
      rhacs-operator-candidate-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- replace --> rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]
      rhacs-operator-candidate-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.1.0)" --> rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]
      rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- replace --> rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- skip --> rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-candidate-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.1.1)" --> rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- "skipRange(>= 4.0.0 < 4.1.1)" --> rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]
      rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]-- replace --> rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- skip --> rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-candidate-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.1.2)" --> rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- "skipRange(>= 4.0.0 < 4.1.2)" --> rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]-- "skipRange(>= 4.0.0 < 4.1.2)" --> rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]
      rhacs-operator-candidate-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]:::deprecated
      rhacs-operator-candidate-rhacs-operator.v4.0.0["rhacs-operator.v4.0.0"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-candidate-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-candidate-rhacs-operator.v4.1.0["rhacs-operator.v4.1.0"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-candidate-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-candidate-rhacs-operator.v4.1.1["rhacs-operator.v4.1.1"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-candidate-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]-- "skipRange(>= 4.0.0 < 4.2.0)" --> rhacs-operator-candidate-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
      rhacs-operator-candidate-rhacs-operator.v4.1.2["rhacs-operator.v4.1.2"]-- replace --> rhacs-operator-candidate-rhacs-operator.v4.2.0["rhacs-operator.v4.2.0"]
    end
  end

style rhacs-operator-candidate fill:#c9c
style rhacs-operator fill:#a9a
```



### Open Questions about cross-catalog interactions (may need to be resolved in RFCs):
- What happens if two catalogs (foo, bar) disagree on whether a package is deprecated?  OLMv0 provides spec.source and spec.sourceNamespace to disambiguate, but how do we leverage those and what are comparable v1 mechanisms?
   - package uniqueness is enforced in opm validate so we could assert that package+channel+version tuple is sufficient to uniquely identify a candidate
   - not enforced in opm render so we’re not consistent
   - we say we want our v1 experience to be analogous to dnf/apt, where add’l contiguous versions could be made available by adding a new source, but then we say that catalogs cannot aggregate, which prevents it

#### A: deprecation is to be considered only w/in the context of the current catalog

- Do we error if someone puts delegation across catalogs?

#### A: delegation has been indefinitely deferred

installation of a thing which has deprecated dependencies -- should generate a warning, not ignore it
bundle authors are not required to create/publish a new bundle in order to deprecate -- bundle immutability

argument that v1 should allow for any-scoped delegation -- inclusive-first, with filtering out as missing specified 




## Old delegation content
The desired experiences include:
authors can communicate sunset strategies to cluster admins with more gradations than moving from “in catalog” to “not in catalog and skipRange required to avoid stranding”
organization can maintain continuous support across disparate branch/versioning strategies
organization can perform quick-response releases which subordinate predecessor releases without having to pretend to be that release and without the risk of stranding some installations (this is also a case served by introducing a ‘release’ field in the bundle version, similar to RPM)
with expansion of delegates to include conditional expressions (for e.g. CEL to validate against a spec field) organization can test new objects in controlled, scaled deployments (i.e. dogfood, canary)




We want to represent these layered relationships in a way that doesn’t clutter catalog specs, and allows them to evolve independently (since the only data dependency on FBC schema is that they have string identifiers). 
The ‘target’ end of these relationships will be expressed, so that there is never a need to retroactively update some object to flag it for delegation/deprecation itself.  There is a security concern here in that we should have some way to verify the catalog contributor is an authorized originator of these relationships.
The relationships between the catalog and its meta-relationships would be collapsed in any opm render-like operation, with the resulting schema … [NB:  consider this deeply, as replaces/skips mechanics could make this complex]

Summarize the RFC(s). Refine, clarify, reiterate the “need” and “benefit”.

Solution Design and Architecture
Metadata is represented as a JSON or YAML blob, either discrete from or co-resident with existing catalog data (like “undecorated” FBC, where no duplicate nodes are allowed).  This will function as FBC does, where the data is composed of the whole hierarchical fs.FS.


```yaml=
schema: olm.catalog.meta
delegates:
- kind: bundle
  ref: foo-stable-v1.0
  delegate: foobar-v0.8
```
delegates a specific bundle+version (foo-stable-v1.0) with another (foobar-v0.8) by name

```yaml=
schema: olm.catalog.meta
delegates:
- kind: channel
  ref: stable-v0
  delegate: foobar-fast
```
delegates a specific channel (stable-v0) with another (foobar-fast) by name

```yaml=
schema: olm.catalog.meta
delegates:
- id: stable-v1
  kind: channelCondition
  condition: (insert CEL here)
  delegate: foobar-canary
```
delegates a channel to be replaced by another (foobar-canary) if a condition evaluates true

```yaml=
schema: olm.catalog.meta
deprecations:
- id: foobar-v0.8
  kind: bundleRange
  bundleRange: <0.6
```
deprecates a range of bundle versions

```yaml=
schema: olm.catalog.meta
deprecations:
- id: foobar-v0.7
  kind: bundle
  bundle: foobar-v0.7
```
deprecates a specific bundle version

```yaml=
schema: olm.catalog.meta
deprecations:
- id: baz
  package: foo
```
deprecates a package by name

-----------------

olm.catalog.meta constraints:
(unambiguous intent) a delegate shall not delegate for both a bundle and a channel
(ordinality) a delegate shall only delegate for a single instance of the selected type
(brevity) a deprecated bundle entry can indicate a single bundle version or a valid semver range expression
(target uniqueness) a deprecated bundle entry shall only indicate one olm.bundle version (e.g. no regexes, globs)
(edge uniqueness) there shall be not more than one delegate for a given bundle version
(parallel channels) a delegate shall not delegate another delegate
(completeness) an unknown id shall fail graph metadata validation



OLM v0 GRPC APIs used
- pkg/controller/registry/registry_client.go
    - GetLatestChannelEntriesThatProvide
    - GetBundle
    - GetPackage
- pkg/package-server/provider/registry.go
    - GetBundleForChannel (*deprecated*)
    - ListBundles
    - ListPackages
    - GetPackage

v0 RFC says:
> The bundle objects provided by the registry v1 GRPC API must include the deprecation properties at minimum for the methods of interest to the resolver(ListBundles) and package-server(GetBundleForChannel)


## DEMO Info


### GRPC
catalog docker.io/grokspawn/deprecation-catalog:latest
demos kiali-operator with a deprecated bundle/channel/package. 

Show the whole yaml structure with deprecations in-place:
```shell=
opm render docker.io/grokspawn/deprecation-catalog:latest -o yaml | yq 'select(.name == "kiali" or .package == "kiali")'
```

(long output)

Show just the deprecations (showing like a real catalog, even though could just dump the single deprecations instance):

```shell=
opm render docker.io/grokspawn/deprecation-catalog:latest -o yaml | yq 'select((.name == "kiali" or .package == "kiali") and .schema == "olm.deprecations")'

```

output:
```yaml=
entries:
  - message: |
      kiali-operator.v1.68.0 is deprecated. Uninstall and install kiali-operator.v1.72.0 for support.
    reference:
      name: kiali-operator.v1.68.0
      schema: olm.bundle
  - message: |
      package kiali is end of life.  Please use 'kiali-new' package for support.
    reference:
      schema: olm.package
  - message: |
      channel alpha is no longer supported.  Please switch to channel 'stable'.
    reference:
      name: alpha
      schema: olm.channel
package: kiali
schema: olm.deprecations
```

Run a catalog instance

```shell=
docker run --rm -it -p 50051:50051 docker.io/grokspawn/deprecation-catalog:latest
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
WARN[0000] unable to set termination log path            error="open /dev/termination-log: permission denied"
INFO[0000] starting pprof endpoint                       address="localhost:6060"
INFO[0000] serving registry                              configs=/configs port=50051
INFO[0000] stopped caching cpu profile data              address="localhost:6060"
```

Interact with the GRPC `ListBundles` API

```shell=
grpcurl -plaintext localhost:50051 api.Registry/ListBundles | jq 'select(.packageName == "kiali" and .csvName == "kiali-operator.v1.68.0")'
```

```json=
{
  "csvName": "kiali-operator.v1.68.0",
  "packageName": "kiali",
  "channelName": "stable",
  "bundlePath": "quay.io/openshift-community-operators/kiali@sha256:61e8f5a82463537409341e6c8886aaf2d43ba360af92e90a1837cd2bccf1580a",
  "providedApis": [
    {
      "group": "kiali.io",
      "version": "v1alpha1",
      "kind": "Kiali"
    }
  ],
  "version": "1.68.0",
  "skipRange": ">=1.0.0 <1.68.0",
  "properties": [
    {
      "type": "olm.gvk",
      "value": "{\"group\":\"kiali.io\",\"kind\":\"Kiali\",\"version\":\"v1alpha1\"}"
    },
    {
      "type": "olm.package",
      "value": "{\"packageName\":\"kiali\",\"version\":\"1.68.0\"}"
    }
  ],
  "replaces": "kiali-operator.v1.67.0",
  "deprecation": {
    "message": "kiali-operator.v1.68.0 is deprecated. Uninstall and install kiali-operator.v1.72.0 for support.\n"
  }
}
```
Hey... look.... deprecations :smile: 


`ListPackages`

```shell=
grpcurl -plaintext localhost:50051 api.Registry/ListPackages | jq '.name'| grep -i -A 5 -B 5 kiali
```

output
```shell=
"group-sync-operator"
"gitops-primer"
"shipwright-operator"
"apimatic-kubernetes-operator"
"machine-deletion-remediation"
"kiali"
"postgresql"
"ack-kms-controller"
"cockroachdb"
"eclipse-che"
"ack-sqs-controller"
```

`GetBundle`
```shell=
grpcurl -plaintext -d '{"pkgName": "kiali", "channelName": "alpha", "csvName": "kiali-operator.v1.68.0"}' localhost:50051 api.Registry/GetBundle
```

```json=
{
  "csvName": "kiali-operator.v1.68.0",
  "packageName": "kiali",
  "channelName": "alpha",
  "bundlePath": "quay.io/openshift-community-operators/kiali@sha256:61e8f5a82463537409341e6c8886aaf2d43ba360af92e90a1837cd2bccf1580a",
  "providedApis": [
    {
      "group": "kiali.io",
      "version": "v1alpha1",
      "kind": "Kiali"
    }
  ],
  "version": "1.68.0",
  "skipRange": "\u003e=1.0.0 \u003c1.68.0",
  "properties": [
    {
      "type": "olm.gvk",
      "value": "{\"group\":\"kiali.io\",\"kind\":\"Kiali\",\"version\":\"v1alpha1\"}"
    },
    {
      "type": "olm.package",
      "value": "{\"packageName\":\"kiali\",\"version\":\"1.68.0\"}"
    }
  ],
  "deprecation": {
    "message": "kiali-operator.v1.68.0 is deprecated. Uninstall and install kiali-operator.v1.72.0 for support.\n"
  }
}
```


We know that 1.68.0 is deprecated, but it also replaces 1.67.  Grab it again but via `GetBundleThatReplaces`
```shell=
grpcurl -plaintext -d '{"pkgName": "kiali", "channelName": "alpha", "csvName": "kiali-operator.v1.67.0"}' localhost:50051 api.Registry/GetBundleThatReplaces
```

```json=
{
  "csvName": "kiali-operator.v1.68.0",
  "packageName": "kiali",
  "channelName": "alpha",
  "bundlePath": "quay.io/openshift-community-operators/kiali@sha256:61e8f5a82463537409341e6c8886aaf2d43ba360af92e90a1837cd2bccf1580a",
  "providedApis": [
    {
      "group": "kiali.io",
      "version": "v1alpha1",
      "kind": "Kiali"
    }
  ],
  "version": "1.68.0",
  "skipRange": "\u003e=1.0.0 \u003c1.68.0",
  "properties": [
    {
      "type": "olm.gvk",
      "value": "{\"group\":\"kiali.io\",\"kind\":\"Kiali\",\"version\":\"v1alpha1\"}"
    },
    {
      "type": "olm.package",
      "value": "{\"packageName\":\"kiali\",\"version\":\"1.68.0\"}"
    }
  ],
  "deprecation": {
    "message": "kiali-operator.v1.68.0 is deprecated. Uninstall and install kiali-operator.v1.72.0 for support.\n"
  }
}
```


### PackageServer Demo
https://asciinema.org/a/xlDhS5xd8ARTqbJ9MVboIPIlK

