Skip to content

Commit c2bdb0c

Browse files
authored
feat(olm): Take the CSV listing metadata from the chart (#132)
1 parent 5a2b91e commit c2bdb0c

2 files changed

Lines changed: 169 additions & 26 deletions

File tree

‎olm/build-manifests.py‎

Lines changed: 164 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import shutil
4040
import subprocess
4141
import sys
42+
import urllib.error
4243
import urllib.parse
4344
import urllib.request
4445

@@ -50,6 +51,8 @@
5051
# under the "sdp" path, e.g. quay.io/stackable/sdp/hbase-operator.
5152
QUAY_REPO = "stackable/sdp"
5253

54+
HUB_API = "https://hub.stackable.tech/api/v1"
55+
5356

5457
class ManifestException(Exception):
5558
pass
@@ -384,12 +387,21 @@ def generate_csv(
384387
"spark-operator" if args.op_name == "spark-k8s-operator" else args.op_name
385388
)
386389

390+
chart = load_chart(args)
391+
listing = CSV_LISTING[args.product]
392+
387393
result["spec"]["version"] = args.release
388-
result["spec"]["keywords"] = [args.product]
389-
result["spec"]["displayName"] = CSV_DISPLAY_NAME[args.product]
394+
result["spec"]["displayName"] = listing["display_name"]
395+
result["spec"]["description"] = chart["description"]
396+
# Charts from before SDP 26.11 carry no keywords.
397+
result["spec"]["keywords"] = chart.get("keywords", [args.product])
398+
result["spec"]["links"] = csv_links(chart)
399+
result["spec"]["minKubeVersion"] = min_kube_version(args.release)
390400
result["metadata"]["name"] = f"{csv_name}.v{args.release}"
391401
result["metadata"]["annotations"]["containerImage"] = related_images[0]["image"]
392-
result["metadata"]["annotations"]["description"] = CSV_DISPLAY_NAME[args.product]
402+
result["metadata"]["annotations"]["description"] = chart["description"]
403+
result["metadata"]["annotations"]["capabilities"] = listing["capabilities"]
404+
result["metadata"]["annotations"]["categories"] = listing["categories"]
393405
result["metadata"]["annotations"]["repository"] = (
394406
f"https://github.com/stackabletech/{args.op_name}"
395407
)
@@ -419,6 +431,80 @@ def generate_csv(
419431
return result
420432

421433

434+
def load_chart(args: argparse.Namespace) -> dict:
435+
chart_path = args.repo_operator / "deploy" / "helm" / args.op_name / "Chart.yaml"
436+
return yaml.load(chart_path.read_text(), Loader=yaml.SafeLoader)
437+
438+
439+
def csv_links(chart: dict) -> list[dict[str, str]]:
440+
"""Turn the chart's Artifact Hub links into CSV links.
441+
442+
>>> links = "[{name: Documentation, url: https://docs.stackable.tech}, {name: support, url: https://github.com}]"
443+
>>> csv_links({"annotations": {"artifacthub.io/links": links}})
444+
[{'name': 'Documentation', 'url': 'https://docs.stackable.tech'}, {'name': 'Support', 'url': 'https://github.com'}]
445+
446+
Charts without the annotation get no links:
447+
448+
>>> csv_links({"description": "An older chart"})
449+
[]
450+
"""
451+
links = yaml.load(
452+
chart.get("annotations", {}).get("artifacthub.io/links", "[]"),
453+
Loader=yaml.SafeLoader,
454+
)
455+
# Artifact Hub only highlights a link named exactly "support"; the console shows names as-is.
456+
return [
457+
{
458+
"name": "Support" if link["name"] == "support" else link["name"],
459+
"url": link["url"],
460+
}
461+
for link in links
462+
]
463+
464+
465+
def hub_release_name(release: str) -> str:
466+
"""Return the name the Stackable Hub uses for a release.
467+
468+
>>> hub_release_name("26.11.0")
469+
'26.11'
470+
>>> hub_release_name("26.7.1")
471+
'26.7'
472+
>>> hub_release_name("0.0.0-dev")
473+
'latest'
474+
"""
475+
if release == "0.0.0-dev":
476+
return "latest"
477+
return ".".join(release.split(".")[:2])
478+
479+
480+
def lowest_kube_version(platforms: list[dict]) -> str:
481+
"""Return the lowest Kubernetes version in the Hub's platform list, as a full version.
482+
483+
Versions compare numerically, so 1.9 is lower than 1.10:
484+
485+
>>> lowest_kube_version([
486+
... {"name": "OpenShift", "versions": ["4.20", "4.21"]},
487+
... {"name": "Kubernetes", "versions": ["1.10", "1.9", "1.11"]},
488+
... ])
489+
'1.9.0'
490+
"""
491+
versions = next(p["versions"] for p in platforms if p["name"] == "Kubernetes")
492+
lowest = min(versions, key=lambda v: tuple(int(x) for x in v.split(".")))
493+
return f"{lowest}.0"
494+
495+
496+
def min_kube_version(release: str) -> str:
497+
"""Return the lowest Kubernetes version the release supports, according to the Stackable Hub."""
498+
hub_release = hub_release_name(release)
499+
url = f"{HUB_API}/releases/{hub_release}"
500+
try:
501+
with urllib.request.urlopen(url) as response:
502+
platforms = json.load(response)["platforms"]
503+
except urllib.error.HTTPError as e:
504+
raise ManifestException(f"Could not read release {hub_release} from {url}: {e}")
505+
return lowest_kube_version(platforms)
506+
507+
422508
def generate_helm_templates(args: argparse.Namespace) -> list[dict]:
423509
logging.debug(f"start generate_helm_templates for {args.repo_operator}")
424510
template_path = args.repo_operator / "deploy" / "helm" / args.repo_operator.name
@@ -592,21 +678,81 @@ def main(argv) -> int:
592678
return 0
593679

594680

595-
CSV_DISPLAY_NAME = {
596-
"airflow": "Stackable Operator for Apache Airflow",
597-
"commons": "Stackable Commons Operator",
598-
"druid": "Stackable Operator for Apache Druid",
599-
"hbase": "Stackable Operator for Apache HBase",
600-
"hdfs": "Stackable Operator for Apache Hadoop HDFS",
601-
"hive": "Stackable Operator for Apache Hive",
602-
"kafka": "Stackable Operator for Apache Kafka",
603-
"nifi": "Stackable Operator for Apache NiFi",
604-
"opa": "Stackable Operator for the Open Policy Agent",
605-
"opensearch": "Stackable Operator for OpenSearch",
606-
"spark-k8s": "Stackable Operator for Apache Spark",
607-
"superset": "Stackable Operator for Apache Superset",
608-
"trino": "Stackable Operator for Trino",
609-
"zookeeper": "Stackable Operator for Apache ZooKeeper",
681+
# OLM-only listing values.
682+
# Everything else the listing shows comes from the operator's Chart.yaml.
683+
# * capabilities: https://sdk.operatorframework.io/docs/overview/operator-capabilities/
684+
# * categories: comma-separated, from the list in https://github.com/operator-framework/api/blob/3d2cbf67cb0990b1b28956bd6eeb21054ca33d0a/pkg/validation/internal/standardcategories.go
685+
CSV_LISTING = {
686+
"airflow": {
687+
"display_name": "Stackable Operator for Apache Airflow",
688+
"capabilities": "Seamless Upgrades",
689+
"categories": "Big Data, Integration & Delivery",
690+
},
691+
"commons": {
692+
"display_name": "Stackable Commons Operator",
693+
"capabilities": "Basic Install",
694+
"categories": "Big Data",
695+
},
696+
"druid": {
697+
"display_name": "Stackable Operator for Apache Druid",
698+
"capabilities": "Seamless Upgrades",
699+
"categories": "Big Data, Database",
700+
},
701+
"hbase": {
702+
"display_name": "Stackable Operator for Apache HBase",
703+
"capabilities": "Seamless Upgrades",
704+
"categories": "Big Data, Database",
705+
},
706+
"hdfs": {
707+
"display_name": "Stackable Operator for Apache Hadoop HDFS",
708+
"capabilities": "Seamless Upgrades",
709+
"categories": "Big Data",
710+
},
711+
"hive": {
712+
"display_name": "Stackable Operator for Apache Hive",
713+
"capabilities": "Seamless Upgrades",
714+
"categories": "Big Data, Database",
715+
},
716+
"kafka": {
717+
"display_name": "Stackable Operator for Apache Kafka",
718+
"capabilities": "Seamless Upgrades",
719+
"categories": "Big Data, Streaming & Messaging",
720+
},
721+
"nifi": {
722+
"display_name": "Stackable Operator for Apache NiFi",
723+
"capabilities": "Seamless Upgrades",
724+
"categories": "Big Data, Integration & Delivery",
725+
},
726+
"opa": {
727+
"display_name": "Stackable Operator for the Open Policy Agent",
728+
"capabilities": "Seamless Upgrades",
729+
"categories": "Big Data, Security",
730+
},
731+
"opensearch": {
732+
"display_name": "Stackable Operator for OpenSearch",
733+
"capabilities": "Seamless Upgrades",
734+
"categories": "Big Data",
735+
},
736+
"spark-k8s": {
737+
"display_name": "Stackable Operator for Apache Spark",
738+
"capabilities": "Seamless Upgrades",
739+
"categories": "Big Data, Integration & Delivery",
740+
},
741+
"superset": {
742+
"display_name": "Stackable Operator for Apache Superset",
743+
"capabilities": "Seamless Upgrades",
744+
"categories": "Big Data",
745+
},
746+
"trino": {
747+
"display_name": "Stackable Operator for Trino",
748+
"capabilities": "Seamless Upgrades",
749+
"categories": "Big Data, Database",
750+
},
751+
"zookeeper": {
752+
"display_name": "Stackable Operator for Apache ZooKeeper",
753+
"capabilities": "Seamless Upgrades",
754+
"categories": "Big Data",
755+
},
610756
}
611757

612758
if __name__ == "__main__":

‎olm/resources/csv.yaml‎

Lines changed: 5 additions & 8 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)