Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fastapi-framework init profile and extension #1868

Merged
merged 10 commits into from
Sep 17, 2024
1 change: 1 addition & 0 deletions charmcraft/application/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
8 changes: 7 additions & 1 deletion charmcraft/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
"""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,
Expand All @@ -42,3 +47,4 @@
register("flask-framework", FlaskFramework)
register("django-framework", DjangoFramework)
register("go-framework", GoFramework)
register("fastapi-framework", FastAPIFramework)
53 changes: 53 additions & 0 deletions charmcraft/extensions/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
},
"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",
},
}

@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"
9 changes: 9 additions & 0 deletions charmcraft/templates/init-fastapi-framework/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
venv/
build/
*.charm
.tox/
.coverage
__pycache__/
*.py[cod]
.idea
.vscode/
59 changes: 59 additions & 0 deletions charmcraft/templates/init-fastapi-framework/charmcraft.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 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
# rabbitmq:
# interface: rabbitmq
# optional: false
# limit: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
paas-app-charmer==1.*
30 changes: 30 additions & 0 deletions charmcraft/templates/init-fastapi-framework/src/charm.py.j2
Original file line number Diff line number Diff line change
@@ -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 }})
77 changes: 75 additions & 2 deletions tests/extensions/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from charmcraft.extensions import apply_extensions
from charmcraft.extensions.app import (
DjangoFramework,
FastAPIFramework,
FlaskFramework,
GoFramework,
)
Expand Down Expand Up @@ -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"},
},
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
Loading