Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions olm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,28 @@ which is the source of the certification process.

## Secret and Listener Operators

The manifest generation for these two operators is only partially automated.
You start with the script below and then manually update the cluster service version.
To generate the manifests for the secret operator version 24.11.1, run:
Use the `scripts/generate-olm.py` script in each operator repository (secret and listener) like this:

```bash
./olm/build-manifests.sh -r 24.11.1 \
-c $HOME/repo/stackable/openshift-certified-operators \
-o $HOME/repo/stackable/secret-operator
```shell
cd $HOME/repo/openshift/openshift-certified-operators/operators/stackable-secret-operator

uv run --script scripts/generate-olm.py \
--output-dir $HOME/repo/openshift/openshift-certified-operators/operators/stackable-secret-operator \
--version <release> \
--openshift-versions v4.18-v4.21
```

Where:

- `-r <release>`: the release number (mandatory). This must be a semver-compatible value to patch-level e.g. 23.1.0.
- `-c <manifest folder>`: the output folder for the manifest files
- `-o <operator-dir>`: directory of the operator repository

Similarly for the listener operator run:

```bash
./olm/build-manifests.sh -r 24.11.1 \
-c $HOME/repo/stackable/openshift-certified-operators \
-o $HOME/repo/stackable/listener-operator
```
- `--version <release>`: the release number (mandatory). Example: `26.3.0`.
- `--output-dir <manifest folder>`: location of the certified operators repository.
- `--openshift-versions <ocp-version-range>`: catalogs where this bundle is published. Example: `v4.18-v4.21`.

## All Other Operators

```bash
./olm/build-manifests.py \
--openshift-versions 'v4.14-v4.16' \
--openshift-versions 'v4.18-v4.21' \
--release 24.11.1 \
--repo-operator ~/repo/stackable/hbase-operator
```
Expand All @@ -71,7 +64,7 @@ To build operator bundles run:
```bash
./olm/build-bundles.sh \
-c $HOME/repo/stackable/openshift-certified-operators \
-r 24.11.1 \
-r 26.3.0 \
-o listener \
-d
```
Expand Down
117 changes: 29 additions & 88 deletions olm/build-manifests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
#!/usr/bin/env python
# vim: filetype=python syntax=python tabstop=4 expandtab
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "pyyaml",
# ]
# ///
"""
(Re)Generate Stackable operator manifests for the Operator Lifecycle Manager (OLM).

The script renders the Helm chart, looks up image digests on
quay.io, and writes a complete OLM bundle under deploy/olm/<version>/.

Usage:

uv run --script olm/generate-olm.py --version 26.3.0 --repo-operator ~/repo/stackable/airflow-operator --output-dir deploy/olm

# Or directly with python3 (PyYAML must be installed):
python3 olm/generate-olm.py --version 26.3.0 --repo-operator ~/repo/stackable/airflow-operator --openshift-versions v4.18-v4.21

Requirements:
- uv (https://docs.astral.sh/uv/) — installs PyYAML automatically
- helm (https://helm.sh)
"""

import argparse
import json
Expand All @@ -13,32 +35,20 @@
import urllib.parse
import urllib.request

try:
import yaml
except ModuleNotFoundError:
print(
"Module 'pyyaml' not found. Install using: pip install -r olm/requirements.txt"
)
sys.exit(1)
import yaml

__version__ = "0.0.1"

DESCRIPTION = """
(Re)Generate manifests for the Operator Lifecycle Manager (OLM).

Example:
./olm/build-manifests.py --release 24.3.0 --repo-operator ~/repo/stackable/airflow-operator
"""


class ManifestException(Exception):
pass


def parse_args(argv: list[str]) -> argparse.Namespace:
"""Parse command line args."""
parser = argparse.ArgumentParser(
description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter
description="Generate OLM bundle manifests for Stackable operators.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"--version",
Expand Down Expand Up @@ -219,8 +229,6 @@ def generate_csv_related_images(

def generate_manifests(args: argparse.Namespace) -> list[dict]:
logging.debug("start generate_manifests")
# Parse CRDs as generated by Rust serde.
crds = generate_crds(args.repo_operator)

# Parse Helm manifests
manifests = generate_helm_templates(args)
Expand Down Expand Up @@ -254,19 +262,16 @@ def generate_manifests(args: argparse.Namespace) -> list[dict]:
except KeyError:
pass

owned_crds = to_owned_crds(crds)

# Generate the CSV
csv = generate_csv(
args,
owned_crds,
cluster_permissions,
deployments,
related_images,
)

logging.debug("finish generate_manifests")
return [csv, *crds, *manifests]
return [csv, *manifests]


def filter_op_objects(args: argparse.Namespace, manifests) -> tuple[dict, dict, dict]:
Expand Down Expand Up @@ -304,7 +309,6 @@ def write_manifests(args: argparse.Namespace, manifests: list[dict]) -> None:
os.makedirs(manifests_dir)

# The following objects are written as separate files:
# - crds
# - cluster service version
# - cluster roles
# - services
Expand Down Expand Up @@ -349,45 +353,8 @@ def write_manifests(args: argparse.Namespace, manifests: list[dict]) -> None:
except FileExistsError:
raise ManifestException("Destintation directory already exists")


def to_owned_crds(crds: list[dict]) -> list[dict]:
logging.debug("start to_owned_crds")
owned_crd_dicts = []
for c in crds:
for v in c["spec"]["versions"]:
### Extract CRD description from different properties
description = "No description available"
try:
# we use this field instead of schema.openAPIV3Schema.description
# because that one is not set by the Rust->CRD serialization
description = v["schema"]["openAPIV3Schema"]["properties"]["spec"][
"description"
]
except KeyError:
pass
try:
# The OPA CRD has this field set
description = v["schema"]["openAPIV3Schema"]["description"]
except KeyError:
pass

owned_crd_dicts.append(
{
"name": c["metadata"]["name"],
"displayName": c["metadata"]["name"],
"kind": c["spec"]["names"]["kind"],
"version": v["name"],
"description": description,
}
)

logging.debug("finish to_owned_crds")
return owned_crd_dicts


def generate_csv(
args: argparse.Namespace,
owned_crds: list[dict],
cluster_permissions: list[tuple[str, dict]],
deployments: list[dict],
related_images: list[dict[str, str]],
Expand All @@ -414,12 +381,6 @@ def generate_csv(
f"https://github.com/stackabletech/{args.op_name}"
)

# Commented out as it caused problems with the certification pipeline.
# result["metadata"]["annotations"]["olm.skipRange"] = f'< {args.release}'

### 1. Add list of owned crds
result["spec"]["customresourcedefinitions"]["owned"] = owned_crds

### 2. Add list of related images
result["spec"]["relatedImages"] = related_images

Expand Down Expand Up @@ -522,26 +483,6 @@ def generate_helm_templates(args: argparse.Namespace) -> list[dict]:
)


def generate_crds(repo_operator: pathlib.Path) -> list[dict]:
logging.debug(f"start generate_crds for {repo_operator}")
crd_path = (
repo_operator / "deploy" / "helm" / repo_operator.name / "crds" / "crds.yaml"
)

logging.info(f"Reading CRDs from {crd_path}")
crds = list(yaml.load_all(crd_path.read_text(), Loader=yaml.SafeLoader))
for crd in crds:
if crd["kind"] == "CustomResourceDefinition":
# Remove the helm.sh/resource-policy annotation
del crd["metadata"]["annotations"]["helm.sh/resource-policy"]
else:
raise ManifestException(
f'Expected "CustomResourceDefinition" but found kind "{crd["kind"]}" in CRD file "{crd_path}"'
)
logging.debug("finish generate_crds")
return crds


def quay_image(images: list[tuple[str, str]]) -> list[dict[str, str]]:
"""Get the images for the operator from quay.io. See: https://docs.quay.io/api/swagger"""
logging.debug("start op_image")
Expand Down
Loading