From 5a8e6bc06111eb15472f585907c2ec00a70e9790 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Fri, 30 Aug 2024 09:57:25 +0200 Subject: [PATCH 1/5] Add FastAPI support for init profile and extension --- charmcraft/application/commands/init.py | 1 + charmcraft/extensions/__init__.py | 4 +- charmcraft/extensions/app.py | 53 +++++++++++++ .../init-fastapi-framework/.gitignore.j2 | 9 +++ .../init-fastapi-framework/charmcraft.yaml.j2 | 55 +++++++++++++ .../requirements.txt.j2 | 1 + .../init-fastapi-framework/src/charm.py.j2 | 30 ++++++++ tests/extensions/test_app.py | 77 ++++++++++++++++++- 8 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 charmcraft/templates/init-fastapi-framework/.gitignore.j2 create mode 100644 charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 create mode 100644 charmcraft/templates/init-fastapi-framework/requirements.txt.j2 create mode 100755 charmcraft/templates/init-fastapi-framework/src/charm.py.j2 diff --git a/charmcraft/application/commands/init.py b/charmcraft/application/commands/init.py index 35345041d..a0dcfe506 100644 --- a/charmcraft/application/commands/init.py +++ b/charmcraft/application/commands/init.py @@ -39,6 +39,7 @@ "flask-framework": "init-flask-framework", "django-framework": "init-django-framework", "go-framework": "init-go-framework", + "fastapi-framework": "init-fastapi-framework", } DEFAULT_PROFILE = "simple" diff --git a/charmcraft/extensions/__init__.py b/charmcraft/extensions/__init__.py index 05467123c..268b8a72b 100644 --- a/charmcraft/extensions/__init__.py +++ b/charmcraft/extensions/__init__.py @@ -17,7 +17,7 @@ """Extension processor and related utilities.""" from charmcraft.extensions._utils import apply_extensions -from charmcraft.extensions.app import DjangoFramework, FlaskFramework, GoFramework +from charmcraft.extensions.app import DjangoFramework, FastAPIFramework, FlaskFramework, GoFramework from charmcraft.extensions.extension import Extension from charmcraft.extensions.registry import ( get_extension_class, @@ -42,3 +42,5 @@ register("flask-framework", FlaskFramework) register("django-framework", DjangoFramework) register("go-framework", GoFramework) +register("fastapi-framework", FastAPIFramework) + diff --git a/charmcraft/extensions/app.py b/charmcraft/extensions/app.py index b7c4bd3a6..c7dc4526a 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -314,3 +314,56 @@ def get_image_name(self) -> str: def get_container_name(self) -> str: """Return name of the container for the app image.""" return "app" + + +class FastAPIFramework(_AppBase): + """Extension for 12-factor FastAPI applications.""" + + framework = "fastapi" + options = { + "webserver-workers": { + "type": "int", + "default": 1, + "description": "Number of workers for uvicorn. Sets env variable WEB_CONCURRENCY. See https://www.uvicorn.org/#command-line-options.", + }, + "webserver-port": { + "type": "int", + "default": 8080, + "description": "Bind to a socket with this port. Default: 8000. Sets env variable UVICORN_PORT.", + }, + "webserver-log-level": { + "type": "string", + "default": "info", + "description": "Set the log level. Options: 'critical', 'error', 'warning', 'info', 'debug', 'trace'. Sets the env variable UVICORN_LOG_LEVEL.", + }, + "metrics-port": { + "type": "int", + "default": 8080, + "description": "Port where the prometheus metrics will be scraped.", + }, + "metrics-path": { + "type": "string", + "default": "/metrics", + "description": "Path where the prometheus metrics will be scraped.", + }, + "secret-key": { + "type": "string", + "description": "Long secret you can use for sessions, csrf or any other thing where you need a random secret shared by all units", + }, + } + + @staticmethod + @override + def get_supported_bases() -> list[tuple[str, str]]: + """Return supported bases.""" + return [("ubuntu", "24.04")] + + @override + def get_image_name(self) -> str: + """Return name of the app image.""" + return "app-image" + + @override + def get_container_name(self) -> str: + """Return name of the container for the app image.""" + return "app" diff --git a/charmcraft/templates/init-fastapi-framework/.gitignore.j2 b/charmcraft/templates/init-fastapi-framework/.gitignore.j2 new file mode 100644 index 000000000..a26d707f9 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework/.gitignore.j2 @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 new file mode 100644 index 000000000..558e94957 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 @@ -0,0 +1,55 @@ +# This file configures Charmcraft. +# See https://juju.is/docs/sdk/charmcraft-config for guidance. + +name: {{ name }} + +type: charm + +base: ubuntu@24.04 + +# the platforms this charm should be built on and run on. +# you can check your architecture with `dpkg --print-architecture` +platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + +# (Required) +summary: A very short one-line summary of the FastAPI application. + +# (Required) +description: | + A comprehensive overview of your FastAPI application. + +extensions: + - fastapi-framework + +# Uncomment the integrations used by your application +# Integrations set to "optional: false" will block the charm +# until the applications are integrated. +# requires: +# mysql: +# interface: mysql_client +# optional: false +# limit: 1 +# postgresql: +# interface: postgresql_client +# optional: false +# limit: 1 +# mongodb: +# interface: mongodb_client +# optional: false +# limit: 1 +# redis: +# interface: redis +# optional: false +# limit: 1 +# s3: +# interface: s3 +# optional: false +# limit: 1 +# saml: +# interface: saml +# optional: false +# limit: 1 diff --git a/charmcraft/templates/init-fastapi-framework/requirements.txt.j2 b/charmcraft/templates/init-fastapi-framework/requirements.txt.j2 new file mode 100644 index 000000000..acab50eb1 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework/requirements.txt.j2 @@ -0,0 +1 @@ +paas-app-charmer==1.* diff --git a/charmcraft/templates/init-fastapi-framework/src/charm.py.j2 b/charmcraft/templates/init-fastapi-framework/src/charm.py.j2 new file mode 100755 index 000000000..440a39d63 --- /dev/null +++ b/charmcraft/templates/init-fastapi-framework/src/charm.py.j2 @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# Copyright {{ year }} {{ author }} +# See LICENSE file for licensing details. + +"""FastAPI Charm entrypoint.""" + +import logging +import typing + +import ops + +import paas_app_charmer.fastapi + +logger = logging.getLogger(__name__) + + +class {{ class_name }}(paas_app_charmer.fastapi.Charm): + """FastAPI Charm service.""" + + def __init__(self, *args: typing.Any) -> None: + """Initialize the instance. + + Args: + args: passthrough to CharmBase. + """ + super().__init__(*args) + + +if __name__ == "__main__": + ops.main.main({{ class_name }}) diff --git a/tests/extensions/test_app.py b/tests/extensions/test_app.py index d88a66c7a..53aedeb39 100644 --- a/tests/extensions/test_app.py +++ b/tests/extensions/test_app.py @@ -19,6 +19,7 @@ from charmcraft.extensions import apply_extensions from charmcraft.extensions.app import ( DjangoFramework, + FastAPIFramework, FlaskFramework, GoFramework, ) @@ -178,14 +179,20 @@ def flask_input_yaml_fixture(): "name": "test-go", "summary": "test summary", "description": "test description", - "bases": [{"name": "ubuntu", "channel": "24.04"}], + "base": "ubuntu@24.04", + "platforms": { + "amd64": None, + }, "extensions": ["go-framework"], }, True, { "actions": GoFramework.actions, "assumes": ["k8s-api"], - "bases": [{"channel": "24.04", "name": "ubuntu"}], + "base": "ubuntu@24.04", + "platforms": { + "amd64": None, + }, "containers": { "app": {"resource": "app-image"}, }, @@ -232,6 +239,72 @@ def flask_input_yaml_fixture(): "type": "charm", }, ), + ( + { + "type": "charm", + "name": "test-fastapi", + "summary": "test summary", + "description": "test description", + "base": "ubuntu@24.04", + "platforms": { + "amd64": None, + }, + "extensions": ["fastapi-framework"], + }, + True, + { + "actions": FastAPIFramework.actions, + "assumes": ["k8s-api"], + "base": "ubuntu@24.04", + "platforms": { + "amd64": None, + }, + "containers": { + "app": {"resource": "app-image"}, + }, + "description": "test description", + "name": "test-fastapi", + "charm-libs": [ + {"lib": "traefik_k8s.ingress", "version": "2"}, + {"lib": "observability_libs.juju_topology", "version": "0"}, + {"lib": "grafana_k8s.grafana_dashboard", "version": "0"}, + {"lib": "loki_k8s.loki_push_api", "version": "0"}, + {"lib": "data_platform_libs.data_interfaces", "version": "0"}, + {"lib": "prometheus_k8s.prometheus_scrape", "version": "0"}, + {"lib": "redis_k8s.redis", "version": "0"}, + {"lib": "data_platform_libs.s3", "version": "0"}, + {"lib": "saml_integrator.saml", "version": "0"}, + ], + "config": { + "options": {**FastAPIFramework.options}, + }, + "parts": { + "charm": { + "plugin": "charm", + "source": ".", + "build-snaps": ["rustup"], + "override-build": "rustup default stable\ncraftctl default", + } + }, + "peers": {"secret-storage": {"interface": "secret-storage"}}, + "provides": { + "metrics-endpoint": {"interface": "prometheus_scrape"}, + "grafana-dashboard": {"interface": "grafana_dashboard"}, + }, + "requires": { + "logging": {"interface": "loki_push_api"}, + "ingress": {"interface": "ingress", "limit": 1}, + }, + "resources": { + "app-image": { + "description": "fastapi application image.", + "type": "oci-image", + }, + }, + "summary": "test summary", + "type": "charm", + }, + ), ], ) def test_apply_extensions_correct(monkeypatch, experimental, tmp_path, input_yaml, expected): From eabd6a34469238c55132d3497cfc29d0d93cd964 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Fri, 30 Aug 2024 10:13:07 +0200 Subject: [PATCH 2/5] Fix linting --- charmcraft/extensions/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/charmcraft/extensions/__init__.py b/charmcraft/extensions/__init__.py index 268b8a72b..5ef75cca7 100644 --- a/charmcraft/extensions/__init__.py +++ b/charmcraft/extensions/__init__.py @@ -17,7 +17,12 @@ """Extension processor and related utilities.""" from charmcraft.extensions._utils import apply_extensions -from charmcraft.extensions.app import DjangoFramework, FastAPIFramework, FlaskFramework, GoFramework +from charmcraft.extensions.app import ( + DjangoFramework, + FastAPIFramework, + FlaskFramework, + GoFramework, +) from charmcraft.extensions.extension import Extension from charmcraft.extensions.registry import ( get_extension_class, @@ -43,4 +48,3 @@ register("django-framework", DjangoFramework) register("go-framework", GoFramework) register("fastapi-framework", FastAPIFramework) - From ae33cdfd5f6d0338c5b6c5bc6626a66656b155eb Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Fri, 30 Aug 2024 11:55:04 +0200 Subject: [PATCH 3/5] Add correct namespace for secret-key --- charmcraft/extensions/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charmcraft/extensions/app.py b/charmcraft/extensions/app.py index c7dc4526a..404fa95a4 100644 --- a/charmcraft/extensions/app.py +++ b/charmcraft/extensions/app.py @@ -346,7 +346,7 @@ class FastAPIFramework(_AppBase): "default": "/metrics", "description": "Path where the prometheus metrics will be scraped.", }, - "secret-key": { + "app-secret-key": { "type": "string", "description": "Long secret you can use for sessions, csrf or any other thing where you need a random secret shared by all units", }, From b5026b9be2fd83c3e6c6cc8a066415d278d841ba Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Fri, 13 Sep 2024 10:34:43 +0200 Subject: [PATCH 4/5] Run smoke tests for all extensions for frameworks --- .../task.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) rename tests/spread/commands/{init-flask-framework => init-extensions}/task.yaml (67%) diff --git a/tests/spread/commands/init-flask-framework/task.yaml b/tests/spread/commands/init-extensions/task.yaml similarity index 67% rename from tests/spread/commands/init-flask-framework/task.yaml rename to tests/spread/commands/init-extensions/task.yaml index 4fdb20425..ee8c09ee0 100644 --- a/tests/spread/commands/init-flask-framework/task.yaml +++ b/tests/spread/commands/init-extensions/task.yaml @@ -1,9 +1,15 @@ -summary: test charmcraft init with flask-framework profile +summary: test charmcraft init with framework profiles priority: 500 # This builds pydantic, so do it early kill-timeout: 75m # Because it builds pydantic, it takes a long time. systems: # We only need to run this test once, and it takes a long time. - ubuntu-22.04-64 +environment: + PROFILE/flask: flask-framework + PROFILE/django: django-framework + PROFILE/go: go-framework + PROFILE/fastapi: fastapi-framework + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "true" execute: | # Required for fetch-libs to succeed since the libraries are not available on @@ -14,7 +20,7 @@ execute: | mkdir -p test-init cd test-init - charmcraft init --profile flask-framework + charmcraft init --profile "${PROFILE}" charmcraft fetch-libs charmcraft pack --verbose test -f *.charm From d9f3fd4ee0c1c8fd21f0e0ec0018105bf62cb971 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 17 Sep 2024 11:52:39 +0200 Subject: [PATCH 5/5] Add rabbitmq integration to init template --- .../templates/init-fastapi-framework/charmcraft.yaml.j2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 b/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 index 558e94957..a8b24a74a 100644 --- a/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 +++ b/charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2 @@ -53,3 +53,7 @@ extensions: # interface: saml # optional: false # limit: 1 +# rabbitmq: +# interface: rabbitmq +# optional: false +# limit: 1