add yaml output for exoscale marketplace

This commit is contained in:
Tobias Brunner 2025-05-30 15:44:37 +02:00
parent 86df11505f
commit bfb64efdec
No known key found for this signature in database
3 changed files with 178 additions and 1 deletions

View file

@ -1,5 +1,10 @@
import re
import pyaml
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse
from django.template.loader import render_to_string
from hub.services.models import ( from hub.services.models import (
ServiceOffering, ServiceOffering,
CloudProvider, CloudProvider,
@ -8,8 +13,8 @@ from hub.services.models import (
ComputePlan, ComputePlan,
VSHNAppCatPrice, VSHNAppCatPrice,
) )
import re
from collections import defaultdict from collections import defaultdict
from markdownify import markdownify
def natural_sort_key(name): def natural_sort_key(name):
@ -79,6 +84,10 @@ def offering_detail(request, provider_slug, service_slug):
service__slug=service_slug, service__slug=service_slug,
) )
# Check if Exoscale marketplace YAML is requested
if request.GET.get("exo_marketplace") == "true":
return generate_exoscale_marketplace_yaml(offering)
pricing_data_by_group_and_service_level = None pricing_data_by_group_and_service_level = None
# Generate pricing data for VSHN offerings # Generate pricing data for VSHN offerings
@ -92,6 +101,92 @@ def offering_detail(request, provider_slug, service_slug):
return render(request, "services/offering_detail.html", context) return render(request, "services/offering_detail.html", context)
def generate_exoscale_marketplace_yaml(offering):
"""Generate YAML structure for Exoscale marketplace"""
# Create service name slug for YAML key
service_slug = offering.service.slug.replace("-", "")
yaml_key = f"marketplace_PRODUCTS_servala-{service_slug}"
# Generate product overview content from service description (convert HTML to Markdown)
product_overview = ""
if offering.service.description:
product_overview = markdownify(
offering.service.description, heading_style="ATX"
)
# Generate highlights content from offering description and offer_description (convert HTML to Markdown)
highlights = ""
if offering.description:
highlights += markdownify(offering.description, heading_style="ATX")
if offering.offer_description:
if highlights:
highlights += "\n\n"
highlights += markdownify(
offering.offer_description.get_full_text(), heading_style="ATX"
)
# Build YAML structure
yaml_structure = {
yaml_key: {
"page_class": "tmpl-marketplace-product",
"html_title": f"Managed {offering.service.name} by VSHN via Servala",
"meta_desc": "Servala is the Open Cloud Native Service Hub. It connects businesses, developers, and cloud service providers on one unique hub with secure, scalable, and easy-to-use cloud-native services.",
"page_header_title": f"Managed {offering.service.name} by VSHN via Servala",
"provider_key": "vshn",
"slug": f"servala-managed-{offering.service.slug}",
"title": f"Managed {offering.service.name} by VSHN via Servala",
"logo": f"img/servala-{offering.service.slug}.svg",
"list_display": [],
"meta": [
{"key": "exoscale-iaas", "value": True},
{"key": "availability", "zones": "all"},
],
"action_link": f"https://servala.com/offering/{offering.cloud_provider.slug}/{offering.service.slug}/?source=exoscale_marketplace",
"action_link_text": "Subscribe now",
"blobs": [
{
"key": "product-overview",
"blob": (
product_overview.strip()
if product_overview
else "Service description not available."
),
},
{
"key": "highlights",
"blob": (
highlights.strip()
if highlights
else "Offering highlights not available."
),
},
"editor",
{
"key": "pricing",
"blob": f"Find all the pricing information on the [Servala website](https://servala.com/offering/{offering.cloud_provider.slug}/{offering.service.slug}/?source=exoscale_marketplace#plans)",
},
{
"key": "service-and-support",
"blob": "Servala is operated by VSHN AG in Zurich, Switzerland.\n\nSeveral SLAs are available on request, offering support 24/7.\n\nMore details can be found in the [VSHN Service Levels Documentation](https://products.vshn.ch/service_levels.html).",
},
{
"key": "terms-of-service",
"blob": "- [Product Description](https://products.vshn.ch/servala/index.html)\n- [General Terms and Conditions](https://products.vshn.ch/legal/gtc_en.html)\n- [SLA](https://products.vshn.ch/service_levels.html)\n- [DPA](https://products.vshn.ch/legal/dpa_en.html)\n- [Privacy Policy](https://products.vshn.ch/legal/privacy_policy_en.html)",
},
],
}
}
# Generate YAML response for browser display
yaml_content = pyaml.dump(yaml_structure, sort_dicts=False)
# Return as plain text for browser display
response = HttpResponse(yaml_content, content_type="text/plain")
return response
def generate_pricing_data(offering): def generate_pricing_data(offering):
"""Generate pricing data for a specific offering and cloud provider""" """Generate pricing data for a specific offering and cloud provider"""
# Fetch compute plans for this cloud provider # Fetch compute plans for this cloud provider

View file

@ -14,8 +14,11 @@ dependencies = [
"django-schema-viewer>=0.5.2", "django-schema-viewer>=0.5.2",
"djangorestframework>=3.15.2", "djangorestframework>=3.15.2",
"environs[django]~=14.0", "environs[django]~=14.0",
"markdownify>=1.1.0",
"odoorpc>=0.10.1", "odoorpc>=0.10.1",
"pillow>=11.1.0", "pillow>=11.1.0",
"pyaml>=25.5.0",
"pyyaml>=6.0.2",
] ]
[project.optional-dependencies] [project.optional-dependencies]

79
uv.lock generated
View file

@ -11,6 +11,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" }, { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" },
] ]
[[package]]
name = "beautifulsoup4"
version = "4.13.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "soupsieve" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" },
]
[[package]] [[package]]
name = "diff-match-patch" name = "diff-match-patch"
version = "20241021" version = "20241021"
@ -202,6 +215,19 @@ django = [
{ name = "django-cache-url" }, { name = "django-cache-url" },
] ]
[[package]]
name = "markdownify"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/78/c48fed23c7aebc2c16049062e72de1da3220c274de59d28c942acdc9ffb2/markdownify-1.1.0.tar.gz", hash = "sha256:449c0bbbf1401c5112379619524f33b63490a8fa479456d41de9dc9e37560ebd", size = 17127, upload-time = "2025-03-05T11:54:40.574Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/11/b751af7ad41b254a802cf52f7bc1fca7cabe2388132f2ce60a1a6b9b9622/markdownify-1.1.0-py3-none-any.whl", hash = "sha256:32a5a08e9af02c8a6528942224c91b933b4bd2c7d078f9012943776fc313eeef", size = 13901, upload-time = "2025-03-05T11:54:39.454Z" },
]
[[package]] [[package]]
name = "marshmallow" name = "marshmallow"
version = "3.26.0" version = "3.26.0"
@ -290,6 +316,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651, upload-time = "2025-01-02T08:12:53.356Z" }, { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651, upload-time = "2025-01-02T08:12:53.356Z" },
] ]
[[package]]
name = "pyaml"
version = "25.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c1/40/94f10f32ab952c5cca713d9ac9d8b2fdc37392d90eea403823eeac674c24/pyaml-25.5.0.tar.gz", hash = "sha256:5799560c7b1c9daf35a7a4535f53e2c30323f74cbd7cb4f2e715b16dd681a58a", size = 29812, upload-time = "2025-05-29T05:34:05.292Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/7d/1b5061beff826f902285827261485a058b943332eba8a5532a0164735205/pyaml-25.5.0-py3-none-any.whl", hash = "sha256:b9e0c4e58a5e8003f8f18e802db49fd0563ada587209b13e429bdcbefa87d035", size = 26422, upload-time = "2025-05-29T05:34:03.594Z" },
]
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.0.1" version = "1.0.1"
@ -308,6 +346,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/43/a2/b6a5cbd5822b4d049adfedf496ce0908480e5a41722fda7b7ffaacb086d6/python_monkey_business-1.1.0-py2.py3-none-any.whl", hash = "sha256:15b4f603c749ba9a7b4f1acd36af023a6c5ba0f7e591c945f8253f0ef44bf389", size = 4670, upload-time = "2024-07-11T16:34:58.565Z" }, { url = "https://files.pythonhosted.org/packages/43/a2/b6a5cbd5822b4d049adfedf496ce0908480e5a41722fda7b7ffaacb086d6/python_monkey_business-1.1.0-py2.py3-none-any.whl", hash = "sha256:15b4f603c749ba9a7b4f1acd36af023a6c5ba0f7e591c945f8253f0ef44bf389", size = 4670, upload-time = "2024-07-11T16:34:58.565Z" },
] ]
[[package]]
name = "pyyaml"
version = "6.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
]
[[package]] [[package]]
name = "servala-fe" name = "servala-fe"
version = "0.1.0" version = "0.1.0"
@ -322,8 +377,11 @@ dependencies = [
{ name = "django-schema-viewer" }, { name = "django-schema-viewer" },
{ name = "djangorestframework" }, { name = "djangorestframework" },
{ name = "environs", extra = ["django"] }, { name = "environs", extra = ["django"] },
{ name = "markdownify" },
{ name = "odoorpc" }, { name = "odoorpc" },
{ name = "pillow" }, { name = "pillow" },
{ name = "pyaml" },
{ name = "pyyaml" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@ -343,11 +401,32 @@ requires-dist = [
{ name = "django-schema-viewer", specifier = ">=0.5.2" }, { name = "django-schema-viewer", specifier = ">=0.5.2" },
{ name = "djangorestframework", specifier = ">=3.15.2" }, { name = "djangorestframework", specifier = ">=3.15.2" },
{ name = "environs", extras = ["django"], specifier = "~=14.0" }, { name = "environs", extras = ["django"], specifier = "~=14.0" },
{ name = "markdownify", specifier = ">=1.1.0" },
{ name = "odoorpc", specifier = ">=0.10.1" }, { name = "odoorpc", specifier = ">=0.10.1" },
{ name = "pillow", specifier = ">=11.1.0" }, { name = "pillow", specifier = ">=11.1.0" },
{ name = "pyaml", specifier = ">=25.5.0" },
{ name = "pyyaml", specifier = ">=6.0.2" },
] ]
provides-extras = ["dev"] provides-extras = ["dev"]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "soupsieve"
version = "2.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
]
[[package]] [[package]]
name = "sqlparse" name = "sqlparse"
version = "0.5.3" version = "0.5.3"