From 2236118e91c99504429f42992e6c4b4461afc81a Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 17 Jan 2025 10:47:01 -0500 Subject: [PATCH 01/51] docs: remove django md file, add django rst file --- ...first-kubernetes-charm-for-a-django-app.md | 646 --------------- ...irst-kubernetes-charm-for-a-django-app.rst | 782 ++++++++++++++++++ 2 files changed, 782 insertions(+), 646 deletions(-) delete mode 100644 docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.md create mode 100644 docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.md b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.md deleted file mode 100644 index fff20d6c8..000000000 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.md +++ /dev/null @@ -1,646 +0,0 @@ -(write-your-first-kubernetes-charm-for-a-django-app)= - -# Write your first Kubernetes charm for a Django app - - -## What you'll need - -- A working station, e.g., a laptop, with amd64 architecture which has sufficient resources to launch a virtual machine with 4 CPUs, 4 GB RAM, and a 50 GB disk - - Note that a workstation with arm64 architecture can complete the majority of this - tutorial. -- Familiarity with Linux. -- About an hour of free time. - - -## What you'll do - -Create a Django application. Use that to create a rock with `rockcraft`. Use that to -create a charm with `charmcraft`. Use that to test-deploy, configure, etc., your Django -application on a local Kubernetes cloud, `microk8s`, with `juju`. All of that multiple -times, mimicking a real development process. - -```{note} -**rock** - -An Ubuntu LTS-based OCI compatible container image designed to meet security, stability, -and reliability requirements for cloud-native software. - -**charm** - -A package consisting of YAML files + Python code that will automate every aspect of an -application's lifecycle so it can be easily orchestrated with Juju. - -**Juju** - -An orchestration engine for charmed applications. -``` - -```{important} -Should you get stuck or notice issues, please get in touch on -[Matrix](https://matrix.to/#/#12-factor-charms:ubuntu.com) or -[Discourse](https://discourse.charmhub.io/). -``` - - -## Set things up - -Install Multipass. - -> See more: [Multipass | How to install -> Multipass](https://multipass.run/docs/install-multipass) - -Use Multipass to launch an Ubuntu VM with the name `charm-dev` from the 22.04 blueprint: - -```bash -multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 22.04 -``` - -Once the VM is up, open a shell into it: - -```bash -multipass shell charm-dev -``` - -In order to create the rock, you'll need to install Rockcraft: - -```bash -sudo snap install rockcraft --classic -``` - -`LXD` will be required for building the rock. Make sure it is installed and initialised: - -```bash -sudo snap install lxd -lxd init --auto -``` - -In order to create the charm, you'll need to install Charmcraft: - -```bash -sudo snap install charmcraft --channel latest/edge --classic -``` - -```{note} -This tutorial requires version `3.2.0` or later of Charmcraft. Check the version -of Charmcraft using `charmcraft --version` If you have an older version of Charmcraft -installed, use `sudo snap refresh charmcraft --channel latest/edge` to get the latest -edge version of Charmcraft. -``` - -MicroK8s is required to deploy the Django application on Kubernetes. Install MicroK8s: - -```bash -sudo snap install microk8s --channel 1.31-strict/stable -sudo adduser $USER snap_microk8s -newgrp snap_microk8s -``` - -Wait for MicroK8s to be ready using `sudo microk8s status --wait-ready`. Several -MicroK8s add-ons are required for deployment: - -```bash -sudo microk8s enable hostpath-storage -# Required to host the OCI image of the Django application -sudo microk8s enable registry -# Required to expose the Django application -sudo microk8s enable ingress -``` - -Juju is required to deploy the Django application. Install Juju and bootstrap a -development controller: - -```bash -sudo snap install juju --channel 3.5/stable -mkdir -p ~/.local/share -juju bootstrap microk8s dev-controller -``` - -Finally, create a new directory for this tutorial and go inside it: - -```bash -mkdir django-hello-world -cd django-hello-world -``` - - -## Create the Django application - -Create a `requirements.txt` file, copy the following text into it and then save it: - -``` -Django -``` - -Install `python3-venv` and create a virtual environment: - -```bash -sudo apt-get update && sudo apt-get install python3-venv -y -python3 -m venv .venv -source .venv/bin/activate -pip install -r requirements.txt -``` - -Create a new project using `django-admin`: - -```bash -django-admin startproject django_hello_world -``` - - -## Run the Django application locally - -Change into the `django_hello_world` directory and run the Django application to verify -that it works: - -```bash -cd django_hello_world -python3 manage.py runserver -``` - -Test the Django application by using `curl` to send a request to the root endpoint. You -may need a new terminal for this; if you are using Multipass use `multipass shell -charm-dev` to get another terminal: - -```bash -curl localhost:8000 -``` - -The Django application should respond with: - -> The install worked successfully! Congratulations! - -```{note} -The response from the Django application includes HTML and CSS which makes it -difficult to read on a terminal. -``` - -The Django application looks good, so you can stop it for now using `Ctrl`+`C`. - - -## Pack the Django application into a rock - -First, we'll need a `rockcraft.yaml` file. Rockcraft will automate its creation and -tailoring for a Django application by using the `django-framework` profile: - -```bash -cd .. -rockcraft init --profile django-framework -``` - -The `rockcraft.yaml` file will automatically be created and set the name based on your -working directory. Open it in a text editor and check that the `name` is -`django-hello-world`. Ensure that `platforms` includes the architecture of your host. -For example, if your host uses the ARM architecture, include `arm64` in `platforms`. - -```{note} -For this tutorial, we'll use the name `django-hello-world` and assume you are on -the `amd64` platform. Check the architecture of your system using `dpkg ---print-architecture`. Choosing a different name or running on a different platform will -influence the names of the files generated by Rockcraft. -``` - -Django applications require a database. Django will use a sqlite database by default. -This won't work on Kubernetes because the database would disappear every time the pod is -restarted (e.g., to perform an upgrade) and this database would not be shared by all -containers as the application is scaled. We'll use Juju later to easily deploy a -database. - -We'll need to update the `settings.py` file to prepare for integrating the app with a -database. Open `django_hello_world/django_hello_world/settings.py` and include `import -json`, `import os` and `import secrets` along with the other imports at the top of the -file. - -Near the top of the `settings.py` file change the following settings to be production -ready: - -```python -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', secrets.token_hex(32)) - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.environ.get('DJANGO_DEBUG', 'false') == 'true' - -ALLOWED_HOSTS = json.loads(os.environ.get('DJANGO_ALLOWED_HOSTS', '{ref}`]')) -``` - -Go further down to the Database section and change the `DATABASES` variable to: - -```python -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.environ.get('POSTGRESQL_DB_NAME'), - 'USER': os.environ.get('POSTGRESQL_DB_USERNAME'), - 'PASSWORD': os.environ.get('POSTGRESQL_DB_PASSWORD'), - 'HOST': os.environ.get('POSTGRESQL_DB_HOSTNAME'), - 'PORT': os.environ.get('POSTGRESQL_DB_PORT'), - } -} -``` - -We'll need to update the `requirements.txt` file to include `psycopg2-binary` so that -the Django app can connect to PostgreSQL. - -Pack the rock: - -```bash -ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack -``` - -```{note} -Depending on your network, this step can take a couple of minutes to finish. - -`ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS` is required whilst the Django extension is -experimental. -``` - -Once Rockcraft has finished packing the Django rock, you'll find a new file in your -working directory with the `.rock` extension: - -```bash -ls *.rock -l -``` - -The rock needs to be copied to the MicroK8s registry so that it can be deployed in the -Kubernetes cluster: - -```bash -rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.1_amd64.rock \ - docker://localhost:32000/django-hello-world:0.1 -``` - -```{note} -If you changed the `name` or `version` in `rockcraft.yaml` or are not on an -`amd64` platform, the name of the `.rock` file will be different for you. -``` - - -## Create the charm - -Create a new directory for the charm and go inside it: - -```bash -mkdir charm -cd charm -``` - -We'll need a `charmcraft.yaml`, `requirements.txt` and source code for the charm. The -source code contains the logic required to operate the Django application. Charmcraft -will automate the creation of these files by using the `django-framework` profile: - -```bash -charmcraft init --profile django-framework --name django-hello-world -``` - -The files will automatically be created in your working directory. We will need to -connect to the PostgreSQL database. Open the `charmcraft.yaml` file and add the -following section to the end of the file: - -```yaml -requires: - postgresql: - interface: postgresql_client - optional: false - limit: 1 -``` - -The charm depends on several libraries. Download the libraries and pack the charm: - -```bash -CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft fetch-libs -CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack -``` - -```{note} -Depending on your network, this step can take a couple of minutes to finish. -``` - -Once Charmcraft has finished packing the charm, you'll find a new file in your working -directory with the `.charm` extension: - -```bash -ls *.charm -l -``` - -```{note} -If you changed the name in charmcraft.yaml or are not on the amd64 platform, the -name of the `.charm` file will be different for you. -``` - - -## Deploy the Django application - -A Juju model is needed to deploy the application. Create a new model: - -```bash -juju add-model django-hello-world -``` - -```{note} -If you are not on a host with the `amd64` architecture, you will need to include -a constraint to the Juju model to specify your architecture.For example, for the `arm64` -architecture, use `juju set-model-constraints -m django-hello-world arch=arm64`. Check -the architecture of your system using `dpkg --print-architecture`. -``` - -Now deploy the Django application using Juju: - -```bash -juju deploy ./django-hello-world_ubuntu-22.04-amd64.charm \ - django-hello-world \ - --resource django-app-image=localhost:32000/django-hello-world:0.1 -``` - -Deploy PostgreSQL, and integrate and PostgreSQL with the Django application: -```bash -juju deploy postgresql-k8s --trust -juju integrate django-hello-world postgresql-k8s -``` - -```{note} -It will take a few minutes to deploy the Django application. You can monitor the -progress using `juju status --watch 5s`. Once the status of the App has gone to -`active`, you can stop watching using `Ctrl+C`. -``` - -The Django application should now be running. You can see the status of the deployment -using `juju status` which should be similar to the following output: - -```output -django-hello-world dev-controller microk8s/localhost 3.5.3 unsupported 16:47:01+10:00 - -App Version Status Scale Charm Channel Rev Address Exposed Message -django-hello-world active 1 django-hello-world 3 10.152.183.126 no -postgresql-k8s 14.11 active 1 postgresql-k8s 14/stable 281 10.152.183.197 no - -Unit Workload Agent Address Ports Message -django-hello-world/0* active idle 10.1.157.80 -postgresql-k8s/0* active idle 10.1.157.78 Primary -``` - -To be able to test the deployment, we need to include the IP address in the allowed -hosts configuration. We'll also enable debug mode for now whilst we are testing. Both -can be done using `juju config django-hello-world django-allowed-hosts=* -django-debug=true`. - -```{note} -Setting the Django allowed hosts to `*` and turning on debug mode should not be -done in production where you should set the actual hostname of the application and -disable debug mode. We will do this in the tutorial for now and later demonstrate how we -can set these to production ready values. -``` - -Test the deployment using `curl` to send a request to the root endpoint. The IP address -is the Address listed in the Unit section of the `juju status` output (e.g., -`10.1.157.80` in the sample output above): - -```bash -curl 10.1.157.80:8000 -``` - -The Django app should again respond with: - -> The install worked successfully! Congratulations! - - -## Add a root endpoint - -The generated Django application does not come with a root endpoint, which is why we had -to initially enable debug mode for testing. Let's add a root endpoint that returns a -`Hello, world!` greeting. We will need to go back out to the root directory for the -tutorial and go into the `django_hello_world` directory using `cd -../django_hello_world`. Add a new Django app using: - -```bash -django-admin startapp greeting -``` - -Open the `greeting/views.py` file and replace the content with: - -```python -from django.http import HttpResponse - -def index(request): - return HttpResponse("Hello, world!\n") -``` - -Create the `greeting/urls.py` file with the following contents: - -```python -from django.urls import path - -from . import views - -urlpatterns = [ - path("", views.index, name="index"), -] -``` - -Open the `django_hello_world/urls.py` file and edit the value of `urlpatterns` to -include `path('', include("greeting.urls")`, for example: - -```python -from django.contrib import admin -from django.urls import include, path - -urlpatterns = [ - path("", include("greeting.urls")), - path("admin/", admin.site.urls), -] -``` - -Since we're changing the application we should update the version of it. Go back to the -root directory of the tutorial using `cd ..` and change the `version` in -`rockcraft.yaml` to `0.2`. Pack and upload the rock using similar commands as before: - -```bash -ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack -rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.2_amd64.rock \ - docker://localhost:32000/django-hello-world:0.2 -``` - -Now we can deploy the new version of the Django application using: - -```bash -cd charm -juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/django-hello-world:0.2 -``` - -Now that we have a valid root endpoint we can disable debug mode: - -```bash -juju config django-hello-world django-debug=false -``` - -Use `juju status --watch 5s` again to wait until the app is active again. The IP address -will have changed so we need to retrieve it again using `juju status`. Now we can call -the root endpoint using `curl 10.1.157.80:8000` and the Django application should -respond with `Hello, world!`. - - -## Enable a configuration - -To demonstrate how to provide configuration to the Django application, we will make the -greeting configurable. Go back out to the tutorial root directory using `cd ..`. Open -the `django_hello_world/greeting/views.py` file and replace the content with: - -```python -import os - -from django.http import HttpResponse - -def index(request): - return HttpResponse(f"{os.environ.get('DJANGO_GREETING', 'Hello, world!')}\n") -``` - -Increment the `version` in `rockcraft.yaml` to `0.3` and run the pack and upload -commands for the rock: - -```bash -ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack -rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.3_amd64.rock \ - docker://localhost:32000/django-hello-world:0.3 -``` - -Change back into the charm directory using `cd charm`. The `django-framework` Charmcraft -extension supports adding configurations in `charmcraft.yaml` which will be passed as -environment variables to the Django application. Add the following to the end of the -`charmcraft.yaml` file: - -```yaml -config: - options: - greeting: - description: | - The greeting to be returned by the Django application. - default: "Hello, world!" - type: string -``` - -```{note} -Configuration options are automatically capitalised and `-` are replaced by `_`. A -`DJANGO_` prefix will also be added as a namespace for app configurations. -``` - -We can now pack and deploy the new version of the Django app: - -```bash -CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack -juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/django-hello-world:0.3 -``` - -After we wait for a bit monitoring `juju status` the application should go back to -`active` again. Sending a request to the root endpoint using `curl 10.1.157.81:8000` -(after getting the IP address from `juju status`) should result in the Django -application responding with `Hello, world!` again. We can change the greeting using -`juju config django-hello-world greeting='Hi!'`. After we wait for a moment for the app -to be restarted, `curl 10.1.157.81:8000` should now respond with `Hi!`. - - -## Expose the app using ingress - -```{note} -This step of the tutorial only works for hosts with the `amd64` architecture. For -other architectures, skip this step. -``` - -As a final step, let's expose the application using ingress. Deploy the -`nginx-ingress-integrator` charm and integrate it with the Django app: - -```bash -juju deploy nginx-ingress-integrator -juju integrate nginx-ingress-integrator django-hello-world -``` - -```{note} -RBAC is enabled in the `charm-dev` Multipass blueprint. Run `juju trust -nginx-ingress-integrator --scope cluster` if you're using the `charm-dev` blueprint. -``` - -The hostname of the app needs to be defined so that it is accessible via the ingress. We will also set the default route to be the root endpoint: - -```bash -juju config nginx-ingress-integrator \ - service-hostname=django-hello-world path-routes=/ -``` - -Monitor `juju status` until everything has a status of `active`. Use `curl -http://django-hello-world --resolve django-hello-world:80:127.0.0.1` to send a request -via the ingress. It should still be returning the `Hi!` greeting. - -```{note} -The `-H "Host: django-hello-world"` option to the `curl` command is a way of setting the -hostname of the request without setting a DNS record. -``` - -We can now also change the Django allowed hosts to `django-hello-world` which is a -production ready value (for production, you will need to setup a DNS record): - -```bash -juju config django-hello-world django-allowed-hosts=django-hello-world -``` - -Running `curl 127.0.0.1 -H "Host: django-hello-world"` should still get the Django app -to respond with `Hi!`. - - -## Tear things down - -You've reached the end of this tutorial. You have created a Django application, deployed -it locally, build an OCI image for it and deployed it using Juju. Then we integrated it -with PostgreSQL to be production ready, demonstrated how to add a root endpoint and how -to configure the application and finally we exposed our application using an ingress. - -If you'd like to reset your working environment, you can run the following in the root -directory for the tutorial: - -```bash -cd .. -deactivate -rm -rf charm .venv django_hello_world -``` - -Then, delete all the files created during the tutorial: - -```bash -rm django-hello-world_0.1_amd64.rock \ - django-hello-world_0.2_amd64.rock \ - django-hello-world_0.3_amd64.rock \ - rockcraft.yaml requirements.txt -``` - -And remove the juju model: - -```bash -juju destroy-model django-hello-world --destroy-storage -``` - -If you created an instance using Multipass, you can also clean it up. Start by exiting it: - -```bash -exit -``` - -And then you can proceed with its deletion: - -```bash -multipass delete charm-dev -multipass purge -``` - - -## Next steps - -By the end of this tutorial you will have built a charm and evolved it in a number of typical ways. But there is a lot more to explore: - -If you are wondering... | Visit... --|- -"How do I...?" | {ref}`how-to-guides` -"What is...?" | {ref}`reference` diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst new file mode 100644 index 000000000..c491a852d --- /dev/null +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -0,0 +1,782 @@ +(write-your-first-kubernetes-charm-for-a-django-app)= + +Write your first Kubernetes charm for a Django app +================================================== + +Imagine you have a Django application backed up by a database such as +PostgreSQL and need to deploy it. In a traditional setup, this can be +quite a challenge, but with Charmcraft you’ll find yourself packaging +and deploying your Django application in no time. Let’s get started! + +In this tutorial we will build a Kubernetes charm for a Django +application using Charmcraft, so we can have a Django application up and +running with Juju. + +This tutorial should take 90 minutes for you to complete. + +.. note:: + + If you're new to the charming world: Django applications are + specifically supported with a coordinated pair of profiles + for an OCI container image (**rock**) and corresponding + packaged software (**charm**) that allow for the application + to be deployed, integrated and operated on a Kubernetes + cluster with the Juju orchestration engine. + +What you’ll need +---------------- + +- A working station, e.g., a laptop, with amd64 or arm64 architecture + which has sufficient resources to launch a virtual machine with 4 + CPUs, 4 GB RAM, and a 50 GB disk. +- Familiarity with Linux. + +What you’ll do +-------------- + +Create a Django application. Use that to create a rock with +``rockcraft``. Use that to create a charm with ``charmcraft``. Use that +to test-deploy, configure, etc., your Django application on a local +Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple +times, mimicking a real development process. + +Set things up +------------- + +.. include:: setup_edge.rst + +Let’s create a new directory for this tutorial and change into it: + +.. code:: bash + + mkdir django-hello-world + cd django-hello-world + +Finally, install ``python3-venv`` and create a virtual environment: + +.. code:: bash + + sudo apt-get update && sudo apt-get install python3-venv -y + python3 -m venv .venv + source .venv/bin/activate + +Create the Django application +----------------------------- + +Create a ``requirements.txt`` file, copy the following text into it and +then save it: + +.. code:: bash + + Django + psycopg2-binary + +.. note:: + + The ``psycopg2-binary`` package is needed so the Django application can + connect to PostgreSQL. + +Install the packages: + +.. code:: bash + + pip install -r requirements.txt + +Create a new project using ``django-admin``: + +.. code:: bash + + django-admin startproject django_hello_world + +Run the Django application locally +---------------------------------- + +We will test the Django application by visiting the app in a web +browser. + +Change into the ``django_hello_world`` directory: + +.. code:: bash + + cd django_hello_world + +Open the settings file of the application located at +``django_hello_world/settings.py``. Update the ``ALLOWED_HOSTS`` setting +to allow all traffic: + +.. code:: python + + ALLOWED_HOSTS = ['*'] + +Save and close the ``settings.py`` file. + +Now, run the Django application to verify that it works: + +.. code:: bash + + python3 manage.py runserver 0.0.0.0:8000 + +.. note:: + + Specifying ``0.0.0.0:8000`` allows for traffic outside of the Multipass VM. + +Now we need the private IP address of the Multipass VM. Use one of the +following options for finding the IP address: + +1. Open a new terminal of the Multipass VM using + ``multipass shell charm-dev`` and run + ``ip route get $(ip route show 0.0.0.0/0 | grep -oP 'via \K\S+') | grep -oP 'src \K\S+'``. +2. Outside of the Multipass VM, run + ``multipass info charm-dev | grep IP``. The ``grep`` command extracts + a portion of the output to highlight the IP address. +3. In the Multipass GUI, under the Instances tab, find the private IP + address listed under the ``charm-dev`` VM. + +With the Multipass IP address, we can visit the Django app in a web +browser. Open a new tab and visit +``http://:8000``, replacing +```` with your VM’s private IP address. + +The Django application should respond in the browser with +``The install worked successfully! Congratulations!``. + +The Django application looks good, so you can stop it for now in the +Multipass VM using :kbd:`Ctrl` + :kbd:`C`. + +Pack the Django application into a rock +--------------------------------------- + +First, we’ll need a ``rockcraft.yaml`` file. Using the +``django-framework`` profile, Rockcraft will automate the creation of +``rockcraft.yaml`` and tailor the file for a Django application. Change +back into the ``django-hello-world`` directory and initialize the rock: + +.. code:: bash + + cd .. + rockcraft init --profile django-framework + +The ``rockcraft.yaml`` file will automatically be created and set the +name based on your working directory, ``django-hello-world``. + +Check out the contents of ``rockcraft.yaml``: + +.. code:: bash + + cat rockcraft.yaml + +The top of the file should look similar to the following snippet: + +.. code:: yaml + + name: django-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Django application + version: '0.1' # just for humans. Semantic versioning is recommended + summary: A summary of your Django application # 79 char long summary + description: | + This is django-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Verify that the ``name`` is ``django-hello-world``. + +Ensure that ``platforms`` includes the architecture of your host. Check +the architecture of your system: + +.. code:: bash + + dpkg --print-architecture + +If your host uses the ARM architecture, open ``rockcraft.yaml`` in a +text editor and include ``arm64`` under ``platforms``. + +Django applications require a database. Django will use a sqlite +database by default. This won’t work on Kubernetes because the database +would disappear every time the pod is restarted (e.g., to perform an +upgrade) and this database would not be shared by all containers as the +application is scaled. We’ll use Juju later to easily deploy a database. + +We’ll need to update the ``settings.py`` file to prepare for integrating +the app with a database. From the ``django-hello-world`` directory, open +``django_hello_world/django_hello_world/settings.py`` and update the +imports to include ``json``, ``os`` and ``secrets``. The top of the +``settings.py`` file should look similar to the following snippet: + +.. code-block:: python + :emphasize-lines: 15,16,17 + + """ + Django settings for django_hello_world project. + + Generated by 'django-admin startproject' using Django 5.1.4. + + For more information on this file, see + https://docs.djangoproject.com/en/5.1/topics/settings/ + + For the full list of settings and their values, see + https://docs.djangoproject.com/en/5.1/ref/settings/ + """ + + from pathlib import Path + + import json + import os + import secrets + +Near the top of the ``settings.py`` file change the following settings +to be production ready: + +.. code-block:: python + :emphasize-lines: 2,5,7 + + # SECURITY WARNING: keep the secret key used in production secret! + SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', secrets.token_hex(32)) + + # SECURITY WARNING: don't run with debug turned on in production! + DEBUG = os.environ.get('DJANGO_DEBUG', 'false') == 'true' + + ALLOWED_HOSTS = json.loads(os.environ.get('DJANGO_ALLOWED_HOSTS', '{ref}`]')) + +We will also use PostgreSQL as the database for our Django app. In +``settings.py``, go further down to the Database section and change the +``DATABASES`` variable to: + +.. code-block:: python + :emphasize-lines: 3-8 + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('POSTGRESQL_DB_NAME'), + 'USER': os.environ.get('POSTGRESQL_DB_USERNAME'), + 'PASSWORD': os.environ.get('POSTGRESQL_DB_PASSWORD'), + 'HOST': os.environ.get('POSTGRESQL_DB_HOSTNAME'), + 'PORT': os.environ.get('POSTGRESQL_DB_PORT'), + } + } + +Save and close the ``settings.py`` file. + +Now let’s pack the rock: + +.. code:: bash + + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + +.. note:: + + ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required whilst the Django + extension is experimental. + +Depending on your network, this step can take a couple of minutes to +finish. Once Rockcraft has finished packing the Django rock, the +terminal will respond with something similar to +``Packed django-hello-world_0.1_amd64.rock``. + +.. note:: + + If you are not on an ``amd64`` platform, the name of the ``.rock`` file + will be different for you. + +The rock needs to be copied to the MicroK8s registry, which stores OCI +archives so they can be downloaded and deployed in a Kubernetes cluster. +Copy the rock: + +.. code:: bash + + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.1_amd64.rock \ + docker://localhost:32000/django-hello-world:0.1 + +.. seealso:: + + See more: `skopeo `_ + +Create the charm +---------------- + +From the ``django-hello-world`` directory, create a new directory for +the charm and change inside it: + +.. code:: bash + + mkdir charm + cd charm + +Using the ``django-framework`` profile, Charmcraft will automate the +creation of the files needed for our charm, including a +``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. +The source code contains the logic required to operate the Django +application. + +Initialize a charm named ``django-hello-world``: + +.. code:: bash + + charmcraft init --profile django-framework --name django-hello-world + +The files will automatically be created in your working directory. + +We will need to connect to the PostgreSQL database. Open the +``charmcraft.yaml`` file and add the following section to the end of the +file: + +.. code:: yaml + + requires: + postgresql: + interface: postgresql_client + optional: false + limit: 1 + +Now let’s pack the charm: + +.. code:: bash + + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + +Depending on your system and network, this step can take a couple of +minutes to finish. + +Once Charmcraft has finished packing the charm, the terminal will +respond with something similar to +``Packed django-hello-world_ubuntu-22.04-amd64.charm``. + +.. note:: + + If you are not on the ``amd64`` platform, the name of the ``.charm`` + file will be different for you. + +Deploy the Django application +----------------------------- + +A Juju model is needed to handle Kubernetes resources while deploying +the Django application. Let’s create a new model: + +.. code:: bash + + juju add-model django-hello-world + +If you are not on a host with the ``amd64`` architecture, you will need +to include a constraint to the Juju model to specify your architecture. +Check the architecture of your system using +``dpkg --print-architecture``. + +For the ``arm64`` architecture, set the model constraints using + +.. code:: bash + + juju set-model-constraints -m django-hello-world arch=arm64 + +Now let’s use the OCI image we previously uploaded to deploy the Django +application. Deploy using Juju by specifying the OCI image name with the +``--resource`` option: + +.. code:: bash + + juju deploy ./django-hello-world_ubuntu-22.04-amd64.charm \ + django-hello-world \ + --resource django-app-image=localhost:32000/django-hello-world:0.1 + +Now let’s deploy PostgreSQL: + +.. code:: bash + + juju deploy postgresql-k8s --trust + +Integrate PostgreSQL with the Django application: + +.. code:: bash + + juju integrate django-hello-world postgresql-k8s + +It will take a few minutes to deploy the Django application. You can +monitor the progress using + +.. code:: bash + + juju status --watch 5s + +It can take a couple of minutes for the apps to finish the deployment. +During this time, the Django app may enter a ``blocked`` state as it +waits to become integrated with the PostgreSQL database. + +Once the status of the App has gone to ``active``, you can stop watching +using :kbd:`Ctrl` + :kbd:`C`. + +The Django application should now be running. You can see the status of +the deployment using ``juju status`` which should be similar to the +following output: + +.. terminal:: + + Model Controller Cloud/Region Version SLA Timestamp + django-hello-world dev-controller microk8s/localhost 3.5.3 unsupported 16:47:01+10:00 + + App Version Status Scale Charm Channel Rev Address Exposed Message + django-hello-world active 1 django-hello-world 3 10.152.183.126 no + postgresql-k8s 14.11 active 1 postgresql-k8s 14/stable 281 10.152.183.197 no + + Unit Workload Agent Address Ports Message + django-hello-world/0* active idle 10.1.157.80 + postgresql-k8s/0* active idle 10.1.157.78 Primary + +To be able to test the deployment, we need to include the IP address in +the allowed hosts configuration. We’ll also enable debug mode for now +whilst we are testing. Set both configurations: + +.. code:: bash + + juju config django-hello-world django-allowed-hosts=* django-debug=true + +.. note:: + + Setting the Django allowed hosts to ``*`` and turning on debug mode should not + be done in production where you should set the actual hostname of the + application and disable debug mode. We will do this in the tutorial for now and + later demonstrate how we can set these to production ready values. + +Let’s expose the application using ingress. Deploy the +``nginx-ingress-integrator`` charm and integrate it with the Django app: + +.. code:: bash + + juju deploy nginx-ingress-integrator + juju integrate nginx-ingress-integrator django-hello-world + +The hostname of the app needs to be defined so that it is accessible via +the ingress. We will also set the default route to be the root endpoint: + +.. code:: bash + + juju config nginx-ingress-integrator \ + service-hostname=django-hello-world path-routes=/ + +Monitor ``juju status`` until everything has a status of ``active``. + +Now we will visit the Django app in a web browser. Outside of the +Multipass VM, open your machine’s ``/etc/hosts`` file in a text editor +and add a line like the following: + +.. code:: bash + + django-hello-world + +Here, replace ```` with the same Multipass VM +private IP address you previously used. + +Now you can open a new tab and visit http://django-hello-world. The +Django app should respond in the browser with +``The install worked successfully! Congratulations!``. + +We can now also change the Django allowed hosts to +``django-hello-world`` which is a production ready value (for +production, you will need to set up a DNS record). Inside the Multipass +VM, set the configuration: + +.. code:: bash + + juju config django-hello-world django-allowed-hosts=django-hello-world + +Visiting http://django-hello-world should still respond with +``The install worked successfully! Congratulations!``. + +Add an initial app +------------------ + +The generated Django application does not come with an app, which is why +we had to initially enable debug mode for testing. Let’s add a greeting +app that returns a ``Hello, world!`` greeting. We will need to go back +out to the ``django-hello-world`` directory where the rock is and enter +into the ``django_hello_world`` directory where the Django application +is. Let’s add a new Django app: + +.. code:: bash + + django-admin startapp greeting + +Open the ``greeting/views.py`` file and replace the content with: + +.. code:: python + + from django.http import HttpResponse + + + def index(request): + return HttpResponse("Hello, world!\n") + +Create the ``greeting/urls.py`` file with the following contents: + +.. code:: python + + from django.urls import path + + from . import views + + urlpatterns = [ + path("", views.index, name="index"), + ] + +Open the ``django_hello_world/urls.py`` file and edit the imports to +contain ``include`` and the value of ``urlpatterns`` to include +``path('', include("greeting.urls")`` like in the following example: + +.. code-block:: python + :emphasize-lines: 2,5 + + from django.contrib import admin + from django.urls import include, path + + urlpatterns = [ + path("", include("greeting.urls")), + path("admin/", admin.site.urls), + ] + +Since we’re changing the application we should update the version of the +rock. Go back to the ``django-hello-world`` directory where the rock is +and change the ``version`` in ``rockcraft.yaml`` to ``0.2``. The top of +the ``rockcraft.yaml`` file should look similar to the following: + +.. code-block:: yaml + :emphasize-lines: 5 + + name: django-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Django application + version: '0.2' # just for humans. Semantic versioning is recommended + summary: A summary of your Django application # 79 char long summary + description: | + This is django-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Now let’s pack and upload the rock using similar commands as before: + +.. code:: bash + + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.2_amd64.rock \ + docker://localhost:32000/django-hello-world:0.2 + +Now we can deploy the new version of the Django application from the +``charm`` directory using: + +.. code:: bash + + cd charm + juju refresh django-hello-world \ + --path=./django-hello-world_ubuntu-22.04-amd64.charm \ + --resource django-app-image=localhost:32000/django-hello-world:0.2 + +Now that we have the greeting app, we can disable debug mode: + +.. code:: bash + + juju config django-hello-world django-debug=false + +Use ``juju status --watch 5s`` again to wait until the App is active +again. You may visit http://django-hello-world from a web browser, or +you can use ``curl 127.0.0.1 -H "Host: django-hello-world"`` inside the +Multipass VM. Either way, the Django application should respond with +``Hello, world!``. + +.. note:: + + The ``-H "Host: django-hello-world"`` option to the ``curl`` command + is a way of setting the hostname of the request without setting a + DNS record. + +Enable a configuration +---------------------- + +To demonstrate how to provide a configuration to the Django application, +we will make the greeting configurable. Go back out to the rock +directory ``django-hello-world`` using ``cd ..``. From there, open the +``django_hello_world/greeting/views.py`` file and replace the content +with: + +.. code:: python + + import os + + from django.http import HttpResponse + + + def index(request): + return HttpResponse(f"{os.environ.get('DJANGO_GREETING', 'Hello, world!')}\n") + +Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code-block:: yaml + :emphasize-lines: 5 + + name: django-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Django application + version: '0.3' # just for humans. Semantic versioning is recommended + summary: A summary of your Django application # 79 char long summary + description: | + This is django-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Let’s run the pack and upload commands for the rock: + +.. code:: bash + + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.3_amd64.rock \ + docker://localhost:32000/django-hello-world:0.3 + +Change back into the charm directory using ``cd charm``. + +The ``django-framework`` Charmcraft extension supports adding +configurations in ``charmcraft.yaml`` which will be passed as +environment variables to the Django application. Add the following to +the end of the ``charmcraft.yaml`` file: + +.. code:: yaml + + config: + options: + greeting: + description: | + The greeting to be returned by the Django application. + default: "Hello, world!" + type: string + +.. note:: + + Configuration options are automatically capitalized and ``-`` are + replaced by ``_``. A ``DJANGO_`` prefix will also be added as a + namespace for app configurations. + +We can now pack and deploy the new version of the Django app: + +.. code:: bash + + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + juju refresh django-hello-world \ + --path=./django-hello-world_ubuntu-22.04-amd64.charm \ + --resource django-app-image=localhost:32000/django-hello-world:0.3 + +After we wait for a bit monitoring ``juju status`` the application +should go back to ``active`` again. Sending a request to the root +endpoint using ``curl 127.0.0.1 -H "Host: django-hello-world"`` or +visiting http://django-hello-world in a web browser should result in the +Django application responding with ``Hello, world!`` again. + +Now let’s change the greeting: + +.. code:: bash + + juju config django-hello-world greeting='Hi!' + +After we wait for a moment for the app to be restarted, using +``curl 127.0.0.1 -H "Host: django-hello-world"`` or visiting +http://django-hello-world should now respond with ``Hi!``. + +Tear things down +---------------- + +We’ve reached the end of this tutorial. We went through the entire +development process, including: + +- Creating a Django application +- Deploying the application locally +- Building an OCI image using Rockcraft +- Packaging the application using Charmcraft +- Deplyoing the application using Juju +- Integrating the application with PostgreSQL to be production ready +- Exposing the application using an ingress +- Adding an initial app and configuring the application + +If you’d like to reset your working environment, you can run the +following in the rock directory ``django-hello-world`` for the tutorial: + +.. code:: bash + + deactivate + rm -rf charm .venv django_hello_world + # delete all the files created during the tutorial + rm django-hello-world_0.1_amd64.rock \ + django-hello-world_0.2_amd64.rock \ + django-hello-world_0.3_amd64.rock \ + rockcraft.yaml requirements.txt + + # Remove the Juju model + juju destroy-model django-hello-world --destroy-storage + +You can also clean up your Multipass instance. Start by exiting it: + +.. code:: bash + + exit + +And then you can proceed with its deletion: + +.. code:: bash + + multipass delete charm-dev + multipass purge + +Next steps +---------- + +By the end of this tutorial you will have built a charm and evolved it +in a number of typical ways. But there is a lot more to explore: + ++--------------------------+------------------------------------------+ +| If you are wondering… | visit… | ++==========================+==========================================+ +| “How do I…?” | :ref:`how-to-guides` | ++--------------------------+------------------------------------------+ +| “How do I debug?” | `Charm debugging | +| | tools `__ | ++--------------------------+------------------------------------------+ +| “How do I get in touch?” | `Matrix | +| | channel `__ | ++--------------------------+------------------------------------------+ +| “What is…?” | :ref:`reference` | ++--------------------------+------------------------------------------+ +| “Why…?”, “So what?” | :ref:`explanation` | ++--------------------------+------------------------------------------+ From 84f07f9caa9e9e4a68f19a92767c68c10581db54 Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 17 Jan 2025 13:10:33 -0500 Subject: [PATCH 02/51] fix: fix lint failures --- ...write-your-first-kubernetes-charm-for-a-django-app.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index c491a852d..a2a94a166 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -426,11 +426,11 @@ following output: django-hello-world dev-controller microk8s/localhost 3.5.3 unsupported 16:47:01+10:00 App Version Status Scale Charm Channel Rev Address Exposed Message - django-hello-world active 1 django-hello-world 3 10.152.183.126 no - postgresql-k8s 14.11 active 1 postgresql-k8s 14/stable 281 10.152.183.197 no + django-hello-world active 1 django-hello-world 3 10.152.183.126 no + postgresql-k8s 14.11 active 1 postgresql-k8s 14/stable 281 10.152.183.197 no Unit Workload Agent Address Ports Message - django-hello-world/0* active idle 10.1.157.80 + django-hello-world/0* active idle 10.1.157.80 postgresql-k8s/0* active idle 10.1.157.78 Primary To be able to test the deployment, we need to include the IP address in @@ -722,7 +722,7 @@ development process, including: - Creating a Django application - Deploying the application locally - Building an OCI image using Rockcraft -- Packaging the application using Charmcraft +- Packaging the application using Charmcraft - Deplyoing the application using Juju - Integrating the application with PostgreSQL to be production ready - Exposing the application using an ingress From 04a48173d2859f6ad8ead309221f129035d20bab Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 17 Jan 2025 14:39:50 -0500 Subject: [PATCH 03/51] docs: add django tutorial spread test --- .../code/django/greeting_charmcraft.yaml | 7 + .../django/postgres_requires_charmcraft.yaml | 4 + .../code/django/settings_init_rock.py | 129 +++++++++ .../code/django/settings_local_run.py | 123 +++++++++ docs/tutorial/code/django/task.yaml | 249 ++++++++++++++++++ .../code/django/urls_django_hello_world.py | 23 ++ docs/tutorial/code/django/urls_greeting.py | 7 + docs/tutorial/code/django/views_greeting.py | 5 + .../django/views_greeting_configuration.py | 7 + 9 files changed, 554 insertions(+) create mode 100644 docs/tutorial/code/django/greeting_charmcraft.yaml create mode 100644 docs/tutorial/code/django/postgres_requires_charmcraft.yaml create mode 100644 docs/tutorial/code/django/settings_init_rock.py create mode 100644 docs/tutorial/code/django/settings_local_run.py create mode 100644 docs/tutorial/code/django/task.yaml create mode 100644 docs/tutorial/code/django/urls_django_hello_world.py create mode 100644 docs/tutorial/code/django/urls_greeting.py create mode 100644 docs/tutorial/code/django/views_greeting.py create mode 100644 docs/tutorial/code/django/views_greeting_configuration.py diff --git a/docs/tutorial/code/django/greeting_charmcraft.yaml b/docs/tutorial/code/django/greeting_charmcraft.yaml new file mode 100644 index 000000000..991e8d937 --- /dev/null +++ b/docs/tutorial/code/django/greeting_charmcraft.yaml @@ -0,0 +1,7 @@ +config: + options: + greeting: + description: | + The greeting to be returned by the Django application. + default: "Hello, world!" + type: string diff --git a/docs/tutorial/code/django/postgres_requires_charmcraft.yaml b/docs/tutorial/code/django/postgres_requires_charmcraft.yaml new file mode 100644 index 000000000..239f014d8 --- /dev/null +++ b/docs/tutorial/code/django/postgres_requires_charmcraft.yaml @@ -0,0 +1,4 @@ +requires: + postgresql: + interface: postgresql_client + optional: false diff --git a/docs/tutorial/code/django/settings_init_rock.py b/docs/tutorial/code/django/settings_init_rock.py new file mode 100644 index 000000000..fcc9b82dd --- /dev/null +++ b/docs/tutorial/code/django/settings_init_rock.py @@ -0,0 +1,129 @@ +""" +Django settings for django_hello_world project. + +Generated by 'django-admin startproject' using Django 5.1.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path +import json +import os +import secrets + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', secrets.token_hex(32)) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = os.environ.get('DJANGO_DEBUG', 'false') == 'true' + +ALLOWED_HOSTS = json.loads(os.environ.get('DJANGO_ALLOWED_HOSTS', '[]')) + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'django_hello_world.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'django_hello_world.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('POSTGRESQL_DB_NAME'), + 'USER': os.environ.get('POSTGRESQL_DB_USERNAME'), + 'PASSWORD': os.environ.get('POSTGRESQL_DB_PASSWORD'), + 'HOST': os.environ.get('POSTGRESQL_DB_HOSTNAME'), + 'PORT': os.environ.get('POSTGRESQL_DB_PORT'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/docs/tutorial/code/django/settings_local_run.py b/docs/tutorial/code/django/settings_local_run.py new file mode 100644 index 000000000..d633191ae --- /dev/null +++ b/docs/tutorial/code/django/settings_local_run.py @@ -0,0 +1,123 @@ +""" +Django settings for django_hello_world project. + +Generated by 'django-admin startproject' using Django 5.1.5. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-o-^flry43te!3t1unbrjw9kmt(4-)yghzfg(j5*0n79s#-km)y' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'django_hello_world.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'django_hello_world.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml new file mode 100644 index 000000000..a0efd4cd5 --- /dev/null +++ b/docs/tutorial/code/django/task.yaml @@ -0,0 +1,249 @@ +########################################### +# IMPORTANT +# Comments matter! +# The docs use the wrapping comments as +# markers for including said instructions +# as snippets in the docs. +########################################### +summary: Getting started with Django tutorial + +kill-timeout: 90m + +environment: + +execute: | + # Move everything to $HOME so that Juju deployment works + mv *.yaml *.py *.txt $HOME + cd $HOME + + # Don't use the staging store for this test + unset CHARMCRAFT_STORE_API_URL + unset CHARMCRAFT_UPLOAD_URL + unset CHARMCRAFT_REGISTRY_URL + + # Add setup instructions + # (Ran into issues in prepare section) + # (don't install charmcraft) + snap install rockcraft --classic + snap install lxd + lxd init --auto + snap install microk8s --channel=1.31-strict/stable + snap refresh juju --channel=3.5/stable --amend + + # Juju config setup + lxc network set lxdbr0 ipv6.address none + mkdir -p ~/.local/share + + # MicroK8s config setup + microk8s status --wait-ready + microk8s enable hostpath-storage + microk8s enable registry + microk8s enable ingress + + # Bootstrap controller + juju bootstrap microk8s dev-controller + + # [docs:create-venv] + sudo apt-get update && sudo apt-get install python3-venv -y + python3 -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + # [docs:create-venv-end] + + django-admin startproject django_hello_world + + # Update settings.py file + cat $HOME/settings_local_run.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py + + # Run and test Django app + python3 manage.py runserver 0.0.0.0:8000 & + retry -n 5 --wait 2 curl --fail localhost:8000 + + curl localhost:8000 | grep Congratulations + + kill $! + + # [docs:create-rockcraft-yaml] + rockcraft init --profile django-framework + # [docs:create-rockcraft-yaml-end] + + sed -i "s/name: .*/name: django-hello-world/g" rockcraft.yaml + sed -i "s/amd64/$(dpkg --print-architecture)/g" rockcraft.yaml + + # Update settings.py file + cat $HOME/settings_init_rock.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py + + # [docs:pack] + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + # [docs:pack-end] + + # [docs:skopeo-copy] + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.1_$(dpkg --print-architecture).rock \ + docker://localhost:32000/django-hello-world:0.1 + # [docs:skopeo-copy-end] + + # [docs:create-charm-dir] + mkdir charm + cd charm + # [docs:create-charm-dir-end] + + # [docs:charm-init] + charmcraft init --profile django-framework --name django-hello-world + # [docs:charm-init-end] + + # Add postgresql_client to charmcraft.yaml + cat $HOME/postgres_requires_charmcraft.yaml >> charmcraft.yaml + + # [docs:charm-pack] + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + # [docs:charm-pack-end] + + # [docs:add-juju-model] + juju add-model django-hello-world + # [docs:add-juju-model-end] + + juju set-model-constraints -m django-hello-world arch=$(dpkg --print-architecture) + + # [docs:deploy-juju-model] + juju deploy ./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + django-hello-world --resource \ + django-app-image=localhost:32000/django-hello-world:0.1 + # [docs:deploy-juju-model-end] + + # [docs:deploy-postgres] + juju deploy postgresql-k8s --trust + # [docs:deploy-postgres-end] + + # [docs:integrate-postgres] + juju integrate django-hello-world postgresql-k8s + # [docs:integrate-postgres-end] + + # Check that django-hello-world and postgres are active idle + juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m + juju wait-for application django-hello-world --query='status=="active"' --timeout 10m + + # Configure allowed-hosts and debug mode + juju config django-hello-world django-allowed-hosts=* django-debug=true + + # [docs:deploy-nginx] + juju deploy nginx-ingress-integrator --channel=latest/edge --base ubuntu@20.04 + juju integrate nginx-ingress-integrator django-hello-world + # [docs:deploy-nginx-end] + + # [docs:config-nginx] + juju config nginx-ingress-integrator \ + service-hostname=django-hello-world path-routes=/ + # [docs:config-nginx-end] + + # give Juju some time to deploy the apps + juju wait-for application django-hello-world --query='status=="active"' --timeout 10m + juju wait-for application nginx-ingress-integrator --query='status=="active"' --timeout 10m + + # curl the Django app + curl http://django-hello-world --resolve django-hello-world:80:127.0.0.1 | grep Congratulations + + # Configure allowed-hosts + juju config django-hello-world django-allowed-hosts=django-hello-world + + # curl again + curl http://django-hello-world --resolve django-hello-world:80:127.0.0.1 | grep Congratulations + + cd .. + cd django_hello_world + + # [docs:startapp-greeting] + django-admin startapp greeting + # [docs:startapp-greeting-end] + + # Update greeting/views.py file + cat $HOME/views_greeting.py > greeting/views.py + + # Create greeting/urls.py file + cat $HOME/urls_greeting.py > greeting/urls.py + + # Update django_hello_world/urls.py file + cat $HOME/urls_django_hello_world.py > django_hello_world/urls.py + + sed -i "s/version: .*/version: 0.2/g" rockcraft.yaml + rockcraft pack + + # [docs:repack-update] + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.2_$(dpkg --print-architecture).rock \ + docker://localhost:32000/django-hello-world:0.2 + # [docs:repack-update-end] + + # [docs:refresh-deployment] + cd charm + juju refresh django-hello-world \ + --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --resource django-app-image=localhost:32000/django-hello-world:0.2 + # [docs:refresh-deployment-end] + + # Disable debug mode + juju config django-hello-world django-debug=false + + # give Juju some time to refresh the app + juju wait-for application django-hello-world --query='status=="active"' --timeout 10m + + # curl and check that the response is Hello, world! + curl 127.0.0.1 -H "Host: django-hello-world" | grep Hello + + cd .. + # Update django_hello_world/greeting/views.py + cat $HOME/views_greeting_configuration.py > django_hello_world/greeting/views.py + + sed -i "s/version: .*/version: 0.3/g" rockcraft.yaml + + # [docs:repack-2nd-update] + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.3_$(dpkg --print-architecture).rock \ + docker://localhost:32000/django-hello-world:0.3 + # [docs:repack-2nd-update-end] + + cd charm + + # Update greeting config in charmcraft.yaml + cat $HOME/greeting_charmcraft.yaml >> ./charmcraft.yaml + + # [docs:repack-refresh-2nd-deployment] + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + juju refresh django-hello-world \ + --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + --resource django-app-image=localhost:32000/django-hello-world:0.3 + # [docs:repack-refresh-2nd-deployment-end] + + # Wait for django-hello-world to be active + juju wait-for application django-hello-world --query='status=="active"' --timeout 10m + + # curl and check that the response is still Hello, world! + curl 127.0.0.1 -H "Host: django-hello-world" | grep Hello + + # [docs:change-config] + juju config django-hello-world greeting='Hi!' + # [docs:change-config-end] + + # make sure that the application updates + juju wait-for application django-hello-world --query='status=="maintenance"' --timeout 10m + juju wait-for application django-hello-world --query='status=="active"' --timeout 10m + + # curl and check that the response is now Hi + curl 127.0.0.1 -H "Host: django-hello-world" | grep Hi + + # Back out to main directory for clean-up + cd .. + + # [docs:clean-environment] + # exit and delete the virtual environment + deactivate + rm -rf charm .venv __pycache__ django_hello_world + # delete all the files created during the tutorial + rm django-hello-world_0.1_$(dpkg --print-architecture).rock django-hello-world_0.2_$(dpkg --print-architecture).rock \ + django-hello-world_0.3_$(dpkg --print-architecture).rock \ + rockcraft.yaml requirements.txt + # Remove the juju model + juju destroy-model django-hello-world --destroy-storage --no-prompt --force + # [docs:clean-environment-end] diff --git a/docs/tutorial/code/django/urls_django_hello_world.py b/docs/tutorial/code/django/urls_django_hello_world.py new file mode 100644 index 000000000..1aab4f0a9 --- /dev/null +++ b/docs/tutorial/code/django/urls_django_hello_world.py @@ -0,0 +1,23 @@ +""" +URL configuration for django_hello_world project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path("", include("greeting.urls")), + path('admin/', admin.site.urls), +] diff --git a/docs/tutorial/code/django/urls_greeting.py b/docs/tutorial/code/django/urls_greeting.py new file mode 100644 index 000000000..5119061b3 --- /dev/null +++ b/docs/tutorial/code/django/urls_greeting.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.index, name="index"), +] diff --git a/docs/tutorial/code/django/views_greeting.py b/docs/tutorial/code/django/views_greeting.py new file mode 100644 index 000000000..d7beb239b --- /dev/null +++ b/docs/tutorial/code/django/views_greeting.py @@ -0,0 +1,5 @@ +from django.http import HttpResponse + + +def index(request): + return HttpResponse("Hello, world!\n") diff --git a/docs/tutorial/code/django/views_greeting_configuration.py b/docs/tutorial/code/django/views_greeting_configuration.py new file mode 100644 index 000000000..b41d2a502 --- /dev/null +++ b/docs/tutorial/code/django/views_greeting_configuration.py @@ -0,0 +1,7 @@ +import os + +from django.http import HttpResponse + + +def index(request): + return HttpResponse(f"{os.environ.get('DJANGO_GREETING', 'Hello, world!')}\n") From 1e9b7b68127c416618b5bae4555bb8359ac0b6af Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 17 Jan 2025 14:46:33 -0500 Subject: [PATCH 04/51] fix: remove trailing spaces --- docs/tutorial/code/django/task.yaml | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index a0efd4cd5..1a69d70a6 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -69,7 +69,7 @@ execute: | sed -i "s/name: .*/name: django-hello-world/g" rockcraft.yaml sed -i "s/amd64/$(dpkg --print-architecture)/g" rockcraft.yaml - + # Update settings.py file cat $HOME/settings_init_rock.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py @@ -91,7 +91,7 @@ execute: | # [docs:charm-init] charmcraft init --profile django-framework --name django-hello-world # [docs:charm-init-end] - + # Add postgresql_client to charmcraft.yaml cat $HOME/postgres_requires_charmcraft.yaml >> charmcraft.yaml @@ -110,15 +110,15 @@ execute: | django-hello-world --resource \ django-app-image=localhost:32000/django-hello-world:0.1 # [docs:deploy-juju-model-end] - + # [docs:deploy-postgres] juju deploy postgresql-k8s --trust # [docs:deploy-postgres-end] - + # [docs:integrate-postgres] juju integrate django-hello-world postgresql-k8s # [docs:integrate-postgres-end] - + # Check that django-hello-world and postgres are active idle juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m juju wait-for application django-hello-world --query='status=="active"' --timeout 10m @@ -142,23 +142,23 @@ execute: | # curl the Django app curl http://django-hello-world --resolve django-hello-world:80:127.0.0.1 | grep Congratulations - + # Configure allowed-hosts juju config django-hello-world django-allowed-hosts=django-hello-world # curl again curl http://django-hello-world --resolve django-hello-world:80:127.0.0.1 | grep Congratulations - + cd .. cd django_hello_world - + # [docs:startapp-greeting] django-admin startapp greeting # [docs:startapp-greeting-end] - + # Update greeting/views.py file cat $HOME/views_greeting.py > greeting/views.py - + # Create greeting/urls.py file cat $HOME/urls_greeting.py > greeting/urls.py @@ -181,7 +181,7 @@ execute: | --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ --resource django-app-image=localhost:32000/django-hello-world:0.2 # [docs:refresh-deployment-end] - + # Disable debug mode juju config django-hello-world django-debug=false @@ -190,22 +190,22 @@ execute: | # curl and check that the response is Hello, world! curl 127.0.0.1 -H "Host: django-hello-world" | grep Hello - + cd .. # Update django_hello_world/greeting/views.py cat $HOME/views_greeting_configuration.py > django_hello_world/greeting/views.py sed -i "s/version: .*/version: 0.3/g" rockcraft.yaml - + # [docs:repack-2nd-update] ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:django-hello-world_0.3_$(dpkg --print-architecture).rock \ docker://localhost:32000/django-hello-world:0.3 # [docs:repack-2nd-update-end] - + cd charm - + # Update greeting config in charmcraft.yaml cat $HOME/greeting_charmcraft.yaml >> ./charmcraft.yaml @@ -215,7 +215,7 @@ execute: | --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ --resource django-app-image=localhost:32000/django-hello-world:0.3 # [docs:repack-refresh-2nd-deployment-end] - + # Wait for django-hello-world to be active juju wait-for application django-hello-world --query='status=="active"' --timeout 10m From da47d6b835a952e37804d5237094d5937fe31c49 Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 17 Jan 2025 14:48:04 -0500 Subject: [PATCH 05/51] fix: remove trailing spaces --- docs/tutorial/code/django/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 1a69d70a6..d60c49438 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -69,7 +69,7 @@ execute: | sed -i "s/name: .*/name: django-hello-world/g" rockcraft.yaml sed -i "s/amd64/$(dpkg --print-architecture)/g" rockcraft.yaml - + # Update settings.py file cat $HOME/settings_init_rock.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py From 5286e23052a97c061b13787a42c2beed629dfe7d Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 17 Jan 2025 21:15:52 -0500 Subject: [PATCH 06/51] fix(store): better error message with fetch-lib (#2084) Fixes #1721 CRAFT-3352 --- charmcraft/application/commands/store.py | 46 +- .../commands/test_store_commands.py | 537 +++++------------- 2 files changed, 180 insertions(+), 403 deletions(-) diff --git a/charmcraft/application/commands/store.py b/charmcraft/application/commands/store.py index 04700b6b5..0d0918ffb 100644 --- a/charmcraft/application/commands/store.py +++ b/charmcraft/application/commands/store.py @@ -1630,18 +1630,20 @@ def run(self, parsed_args: argparse.Namespace) -> None: # get tips from the Store store = Store(env.get_store_config(), needs_auth=False) - to_query = [] - for lib in local_libs_data: - if lib.lib_id is None: - item = { - "charm_name": lib.charm_name, - "lib_name": lib.lib_name, - "api": lib.api, - } - else: - item = {"lib_id": lib.lib_id, "api": lib.api} - to_query.append(item) - libs_tips = store.get_libraries_tips(to_query) + try: + libs_tips = self._services.store.get_libraries_metadata( + [ + project.CharmLib( + lib=f"{lib.charm_name}.{lib.lib_name}", version=str(lib.api) + ) + for lib in local_libs_data + ] + ) + except errors.LibraryError: + raise errors.LibraryError( + message=f"Library {parsed_args.library} not found in Charmhub.", + logpath_report=False, + ) # check if something needs to be done analysis = [] @@ -1649,7 +1651,7 @@ def run(self, parsed_args: argparse.Namespace) -> None: emit.debug(f"Verifying local lib {lib_data}") # fix any missing lib id using the Store info if lib_data.lib_id is None: - for tip in libs_tips.values(): + for tip in libs_tips: if ( lib_data.charm_name == tip.charm_name and lib_data.lib_name == tip.lib_name @@ -1657,18 +1659,20 @@ def run(self, parsed_args: argparse.Namespace) -> None: lib_data = dataclasses.replace(lib_data, lib_id=tip.lib_id) break - tip = libs_tips.get((lib_data.lib_id, lib_data.api)) + for tip in libs_tips: + if tip.lib_id == lib_data.lib_id and tip.api == lib_data.api: + break + else: + raise errors.LibraryError( + message=f"Library {parsed_args.library} not found in Charmhub.", + logpath_report=False, + ) emit.debug(f"Store tip: {tip}") error_message = None - if tip is None: - error_message = f"Library {lib_data.full_name} not found in Charmhub." - elif tip.patch > lib_data.patch: - # the store has a higher version than local - pass - elif tip.patch < lib_data.patch: + if tip.patch < lib_data.patch: # the store has a lower version numbers than local error_message = f"Library {lib_data.full_name} has local changes, cannot be updated." - else: + elif tip.patch == lib_data.patch: # same versions locally and in the store if tip.content_hash == lib_data.content_hash: error_message = ( diff --git a/tests/integration/commands/test_store_commands.py b/tests/integration/commands/test_store_commands.py index a49f3c352..1cb23944c 100644 --- a/tests/integration/commands/test_store_commands.py +++ b/tests/integration/commands/test_store_commands.py @@ -17,18 +17,25 @@ import argparse import datetime +import json +import pathlib +import re import sys from unittest import mock +import craft_cli.pytest_plugin import pytest from craft_store import publisher -from charmcraft import env +from charmcraft import env, errors, utils from charmcraft.application.commands import FetchLibCommand from charmcraft.application.commands.store import CreateTrack -from charmcraft.store.models import Library from tests import factory +OPERATOR_LIBS_LINUX_APT_ID = "7c3dbc9c2ad44a47bd6fcb25caa270e5" +OPERATOR_LIBS_LINUX_SNAP_ID = "05394e5893f94f2d90feb7cbe6b633cd" +MYSQL_MYSQL_ID = "8c1428f06b1b4ec8bf98b7d980a38a8c" + @pytest.fixture def store_mock(): @@ -47,469 +54,235 @@ def validate_params(config, ephemeral=False, needs_auth=True): # region fetch-lib tests +@pytest.mark.slow @pytest.mark.parametrize("formatted", [None, "json"]) def test_fetchlib_simple_downloaded( - emitter, store_mock, tmp_path, monkeypatch, config, formatted + emitter: craft_cli.pytest_plugin.RecordingEmitter, + new_path: pathlib.Path, + config, + formatted: str | None, ): """Happy path fetching the lib for the first time (downloading it).""" - monkeypatch.chdir(tmp_path) - - lib_id = "test-example-lib-id" - lib_content = "some test content with uñicode ;)" - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="testcharm", - ), - } - store_mock.get_library.return_value = Library( - lib_id=lib_id, - content=lib_content, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="testcharm", + saved_file = new_path / utils.get_lib_path("operator_libs_linux", "apt", 0) + args = argparse.Namespace( + library="charms.operator_libs_linux.v0.apt", format=formatted ) - - args = argparse.Namespace(library="charms.testcharm.v0.testlib", format=formatted) FetchLibCommand(config).run(args) - assert store_mock.mock_calls == [ - mock.call.get_libraries_tips( - [{"charm_name": "testcharm", "lib_name": "testlib", "api": 0}] - ), - mock.call.get_library("testcharm", lib_id, 0), - ] - if formatted: - expected = [ - { - "charm_name": "testcharm", - "library_name": "testlib", - "library_id": lib_id, - "api": 0, - "fetched": { - "patch": 7, - "content_hash": "abc", - }, - }, - ] - emitter.assert_json_output(expected) - else: - expected = "Library charms.testcharm.v0.testlib version 0.7 downloaded." - emitter.assert_message(expected) - saved_file = tmp_path / "lib" / "charms" / "testcharm" / "v0" / "testlib.py" - assert saved_file.read_text() == lib_content + assert saved_file.exists() + message = emitter.interactions[-1].args[1] -def test_fetchlib_simple_dash_in_name( - emitter, store_mock, tmp_path, monkeypatch, config -): - """Happy path fetching the lib for the first time (downloading it).""" - monkeypatch.chdir(tmp_path) - - lib_id = "test-example-lib-id" - lib_content = "some test content with uñicode ;)" - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="test-charm", - ), - } - store_mock.get_library.return_value = Library( - lib_id=lib_id, - content=lib_content, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="test-charm", - ) - - args = argparse.Namespace(library="charms.test_charm.v0.testlib", format=None) - FetchLibCommand(config).run(args) + if formatted: + message_dict = json.loads(message)[0] + assert isinstance(message_dict["fetched"]["patch"], int) + assert len(message_dict["fetched"]["content_hash"]) == 64 # sha256 hash + del message_dict["fetched"] + assert message_dict == { + "charm_name": "operator-libs-linux", + "library_name": "apt", + "library_id": OPERATOR_LIBS_LINUX_APT_ID, + "api": 0, + } + else: + assert re.match( + r"Library charms\.operator_libs_linux\.v0\.apt version 0.[0-9]+ downloaded.", + message, + ) - assert store_mock.mock_calls == [ - mock.call.get_libraries_tips( - [{"charm_name": "test-charm", "lib_name": "testlib", "api": 0}] - ), - mock.call.get_library("test-charm", lib_id, 0), - ] - expected = "Library charms.test_charm.v0.testlib version 0.7 downloaded." - emitter.assert_message(expected) - saved_file = tmp_path / "lib" / "charms" / "test_charm" / "v0" / "testlib.py" - assert saved_file.read_text() == lib_content + lib = utils.get_lib_info(lib_path=saved_file) + assert lib.api == 0 + assert lib.charm_name == "operator-libs-linux" + assert lib.lib_name == "apt" + assert lib.lib_id == OPERATOR_LIBS_LINUX_APT_ID + assert lib.patch > 1 -def test_fetchlib_simple_dash_in_name_on_disk( - emitter, store_mock, tmp_path, monkeypatch, config +@pytest.mark.slow +def test_fetchlib_simple_updated( + emitter: craft_cli.pytest_plugin.RecordingEmitter, new_path: pathlib.Path, config ): - """Happy path fetching the lib for the first time (downloading it).""" - monkeypatch.chdir(tmp_path) - - lib_id = "test-example-lib-id" - lib_content = "test-content" - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="test-charm", - ), - } - store_mock.get_library.return_value = Library( - lib_id=lib_id, - content=lib_content, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="test-charm", + """Happy path fetching the lib for Nth time (updating it).""" + content, content_hash = factory.create_lib_filepath( + "operator-libs-linux", "apt", api=0, patch=1, lib_id=OPERATOR_LIBS_LINUX_APT_ID ) - factory.create_lib_filepath("test-charm", "testlib", api=0, patch=1, lib_id=lib_id) - args = argparse.Namespace(library=None, format=None) + args = argparse.Namespace(library="charms.operator_libs_linux.v0.apt", format=None) FetchLibCommand(config).run(args) - assert store_mock.mock_calls == [ - mock.call.get_libraries_tips([{"lib_id": "test-example-lib-id", "api": 0}]), - mock.call.get_library("test-charm", lib_id, 0), - ] - expected = "Library charms.test_charm.v0.testlib updated to version 0.7." - emitter.assert_message(expected) - - -def test_fetchlib_simple_updated(emitter, store_mock, tmp_path, monkeypatch, config): - """Happy path fetching the lib for Nth time (updating it).""" - monkeypatch.chdir(tmp_path) + message = emitter.interactions[-1].args[1] - lib_id = "test-example-lib-id" - content, content_hash = factory.create_lib_filepath( - "testcharm", "testlib", api=0, patch=1, lib_id=lib_id + assert re.match( + r"Library charms\.operator_libs_linux\.v0\.apt updated to version 0\.[0-9]+\.", + message, ) - new_lib_content = "some test content with uñicode ;)" - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash="abc", - api=0, - patch=2, - lib_name="testlib", - charm_name="testcharm", - ), - } - store_mock.get_library.return_value = Library( - lib_id=lib_id, - content=new_lib_content, - content_hash="abc", - api=0, - patch=2, - lib_name="testlib", - charm_name="testcharm", - ) - - args = argparse.Namespace(library="charms.testcharm.v0.testlib", format=None) - FetchLibCommand(config).run(args) - - assert store_mock.mock_calls == [ - mock.call.get_libraries_tips([{"lib_id": lib_id, "api": 0}]), - mock.call.get_library("testcharm", lib_id, 0), - ] - expected = "Library charms.testcharm.v0.testlib updated to version 0.2." - emitter.assert_message(expected) - saved_file = tmp_path / "lib" / "charms" / "testcharm" / "v0" / "testlib.py" - assert saved_file.read_text() == new_lib_content + saved_file = new_path / utils.get_lib_path("operator_libs_linux", "apt", 0) + lib = utils.get_lib_info(lib_path=saved_file) + assert lib.api == 0 + assert lib.charm_name == "operator-libs-linux" + assert lib.lib_name == "apt" + assert lib.lib_id == OPERATOR_LIBS_LINUX_APT_ID + assert lib.patch > 1 +@pytest.mark.slow @pytest.mark.skipif(sys.platform == "win32", reason="Windows not [yet] supported") @pytest.mark.parametrize("formatted", [None, "json"]) -def test_fetchlib_all(emitter, store_mock, tmp_path, monkeypatch, config, formatted): +def test_fetchlib_all( + emitter: craft_cli.pytest_plugin.RecordingEmitter, + new_path: pathlib.Path, + config, + formatted: str | None, +): """Update all the libraries found in disk.""" - monkeypatch.chdir(tmp_path) - - c1, h1 = factory.create_lib_filepath( - "testcharm1", "testlib1", api=0, patch=1, lib_id="lib_id_1" - ) - c2, h2 = factory.create_lib_filepath( - "testcharm2", "testlib2", api=3, patch=5, lib_id="lib_id_2" + factory.create_lib_filepath( + "operator-libs-linux", + "snap", + api=0, + patch=1, + lib_id=OPERATOR_LIBS_LINUX_SNAP_ID, ) - - store_mock.get_libraries_tips.return_value = { - ("lib_id_1", 0): Library( - lib_id="lib_id_1", - content=None, - content_hash="abc", - api=0, - patch=2, - lib_name="testlib1", - charm_name="testcharm1", - ), - ("lib_id_2", 3): Library( - lib_id="lib_id_2", - content=None, - content_hash="def", - api=3, - patch=14, - lib_name="testlib2", - charm_name="testcharm2", - ), - } - _store_libs_info = [ - Library( - lib_id="lib_id_1", - content="new lib content 1", - content_hash="xxx", - api=0, - patch=2, - lib_name="testlib1", - charm_name="testcharm1", - ), - Library( - lib_id="lib_id_2", - content="new lib content 2", - content_hash="yyy", - api=3, - patch=14, - lib_name="testlib2", - charm_name="testcharm2", - ), - ] - store_mock.get_library.side_effect = lambda *a: _store_libs_info.pop(0) + factory.create_lib_filepath("mysql", "mysql", api=0, patch=1, lib_id=MYSQL_MYSQL_ID) args = argparse.Namespace(library=None, format=formatted) FetchLibCommand(config).run(args) + message = emitter.interactions[-1].args[1] - assert store_mock.mock_calls == [ - mock.call.get_libraries_tips( - [ - {"lib_id": "lib_id_1", "api": 0}, - {"lib_id": "lib_id_2", "api": 3}, - ] - ), - mock.call.get_library("testcharm1", "lib_id_1", 0), - mock.call.get_library("testcharm2", "lib_id_2", 3), - ] - names = [ - "charms.testcharm1.v0.testlib1", - "charms.testcharm2.v3.testlib2", - ] - emitter.assert_debug("Libraries found under 'lib/charms': " + str(names)) if formatted: - expected = [ + message_list = json.loads(message) + for message_dict in message_list: + assert isinstance(message_dict["fetched"]["patch"], int) + assert len(message_dict["fetched"]["content_hash"]) == 64 # sha256 hash + del message_dict["fetched"] + assert message_list == [ { - "charm_name": "testcharm1", - "library_name": "testlib1", - "library_id": "lib_id_1", + "charm_name": "mysql", + "library_name": "mysql", + "library_id": MYSQL_MYSQL_ID, "api": 0, - "fetched": { - "patch": 2, - "content_hash": "xxx", - }, }, { - "charm_name": "testcharm2", - "library_name": "testlib2", - "library_id": "lib_id_2", - "api": 3, - "fetched": { - "patch": 14, - "content_hash": "yyy", - }, + "charm_name": "operator-libs-linux", + "library_name": "snap", + "library_id": OPERATOR_LIBS_LINUX_SNAP_ID, + "api": 0, }, ] - emitter.assert_json_output(expected) else: - emitter.assert_messages( - [ - "Library charms.testcharm1.v0.testlib1 updated to version 0.2.", - "Library charms.testcharm2.v3.testlib2 updated to version 3.14.", - ] + assert re.match( + r"Library charms\.[a-z_]+\.v0\.[a-z]+ updated to version 0\.[0-9]+\.", + message, ) - saved_file = tmp_path / "lib" / "charms" / "testcharm1" / "v0" / "testlib1.py" - assert saved_file.read_text() == "new lib content 1" - saved_file = tmp_path / "lib" / "charms" / "testcharm2" / "v3" / "testlib2.py" - assert saved_file.read_text() == "new lib content 2" + saved_file = new_path / utils.get_lib_path("operator_libs_linux", "snap", 0) + lib = utils.get_lib_info(lib_path=saved_file) + assert lib.api == 0 + assert lib.charm_name == "operator-libs-linux" + assert lib.lib_name == "snap" + assert lib.lib_id == OPERATOR_LIBS_LINUX_SNAP_ID + assert lib.patch > 1 + + saved_file = new_path / utils.get_lib_path("mysql", "mysql", 0) + lib = utils.get_lib_info(lib_path=saved_file) + assert lib.api == 0 + assert lib.charm_name == "mysql" + assert lib.lib_name == "mysql" + assert lib.lib_id == MYSQL_MYSQL_ID + assert lib.patch > 1 +@pytest.mark.slow @pytest.mark.parametrize("formatted", [None, "json"]) -def test_fetchlib_store_not_found(emitter, store_mock, config, formatted): +def test_fetchlib_store_not_found( + emitter: craft_cli.pytest_plugin.RecordingEmitter, + new_path: pathlib.Path, + config, + formatted: str | None, +) -> None: """The indicated library is not found in the store.""" - store_mock.get_libraries_tips.return_value = {} args = argparse.Namespace(library="charms.testcharm.v0.testlib", format=formatted) - FetchLibCommand(config).run(args) - ( - store_mock.get_libraries_tips.assert_called_once_with( - [{"charm_name": "testcharm", "lib_name": "testlib", "api": 0}] - ), + with pytest.raises(errors.LibraryError) as exc_info: + FetchLibCommand(config).run(args) + + assert exc_info.value.args[0] == ( + "Library charms.testcharm.v0.testlib not found in Charmhub." ) - error_message = "Library charms.testcharm.v0.testlib not found in Charmhub." - if formatted: - expected = [ - { - "charm_name": "testcharm", - "library_name": "testlib", - "library_id": None, - "api": 0, - "error_message": error_message, - }, - ] - emitter.assert_json_output(expected) - else: - emitter.assert_message(error_message) +@pytest.mark.slow @pytest.mark.parametrize("formatted", [None, "json"]) def test_fetchlib_store_is_old( - emitter, store_mock, tmp_path, monkeypatch, config, formatted + emitter: craft_cli.pytest_plugin.RecordingEmitter, + new_path: pathlib.Path, + config, + formatted: str | None, ): """The store has an older version that what is found locally.""" - monkeypatch.chdir(tmp_path) - - lib_id = "test-example-lib-id" - factory.create_lib_filepath("testcharm", "testlib", api=0, patch=7, lib_id=lib_id) - - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash="abc", - api=0, - patch=6, - lib_name="testlib", - charm_name="testcharm", - ), - } - args = argparse.Namespace(library="charms.testcharm.v0.testlib", format=formatted) + factory.create_lib_filepath( + "mysql", "mysql", api=0, patch=2**63, lib_id=MYSQL_MYSQL_ID + ) + + args = argparse.Namespace(library="charms.mysql.v0.mysql", format=formatted) FetchLibCommand(config).run(args) - store_mock.get_libraries_tips.assert_called_once_with( - [{"lib_id": lib_id, "api": 0}] - ) error_message = ( - "Library charms.testcharm.v0.testlib has local changes, cannot be updated." + "Library charms.mysql.v0.mysql has local changes, cannot be updated." ) if formatted: expected = [ { - "charm_name": "testcharm", - "library_name": "testlib", - "library_id": lib_id, + "charm_name": "mysql", + "library_name": "mysql", + "library_id": MYSQL_MYSQL_ID, "api": 0, "error_message": error_message, }, ] - emitter.assert_json_output(expected) + emitter.assert_json_output( # pyright: ignore[reportAttributeAccessIssue] + expected + ) else: emitter.assert_message(error_message) -@pytest.mark.parametrize("formatted", [None, "json"]) +@pytest.mark.slow def test_fetchlib_store_same_versions_same_hash( - emitter, store_mock, tmp_path, monkeypatch, config, formatted + emitter: craft_cli.pytest_plugin.RecordingEmitter, new_path: pathlib.Path, config ): """The store situation is the same than locally.""" - monkeypatch.chdir(tmp_path) - - lib_id = "test-example-lib-id" - _, c_hash = factory.create_lib_filepath( - "testcharm", "testlib", api=0, patch=7, lib_id=lib_id - ) + args = argparse.Namespace(library="charms.operator_libs_linux.v0.snap", format=None) + # This run is a setup run + FetchLibCommand(config).run(args) - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash=c_hash, - api=0, - patch=7, - lib_name="testlib", - charm_name="testcharm", - ), - } - args = argparse.Namespace(library="charms.testcharm.v0.testlib", format=formatted) + # The real run FetchLibCommand(config).run(args) - store_mock.get_libraries_tips.assert_called_once_with( - [{"lib_id": lib_id, "api": 0}] + assert re.match( + r"Library charms.operator_libs_linux.v0.snap was already up to date in version 0.[0-9]+.", + emitter.interactions[-1].args[1], ) - error_message = ( - "Library charms.testcharm.v0.testlib was already up to date in version 0.7." - ) - if formatted: - expected = [ - { - "charm_name": "testcharm", - "library_name": "testlib", - "library_id": lib_id, - "api": 0, - "error_message": error_message, - }, - ] - emitter.assert_json_output(expected) - else: - emitter.assert_message(error_message) -@pytest.mark.parametrize("formatted", [None, "json"]) +@pytest.mark.slow def test_fetchlib_store_same_versions_different_hash( - emitter, store_mock, tmp_path, monkeypatch, config, formatted + emitter: craft_cli.pytest_plugin.RecordingEmitter, new_path: pathlib.Path, config ): """The store has the lib in the same version, but with different content.""" - monkeypatch.chdir(tmp_path) - - lib_id = "test-example-lib-id" - factory.create_lib_filepath("testcharm", "testlib", api=0, patch=7, lib_id=lib_id) - - store_mock.get_libraries_tips.return_value = { - (lib_id, 0): Library( - lib_id=lib_id, - content=None, - content_hash="abc", - api=0, - patch=7, - lib_name="testlib", - charm_name="testcharm", - ), - } - args = argparse.Namespace(library="charms.testcharm.v0.testlib", format=formatted) + args = argparse.Namespace(library="charms.operator_libs_linux.v0.snap", format=None) + lib_path = utils.get_lib_path("operator-libs-linux", "snap", 0) + # This run is a setup run FetchLibCommand(config).run(args) + with lib_path.open("a+") as f: + f.write("# This changes the hash!") - assert store_mock.mock_calls == [ - mock.call.get_libraries_tips([{"lib_id": lib_id, "api": 0}]), - ] - error_message = ( - "Library charms.testcharm.v0.testlib has local changes, cannot be updated." + # The real run + FetchLibCommand(config).run(args) + + assert emitter.interactions[-1].args[1] == ( + "Library charms.operator_libs_linux.v0.snap has local changes, cannot be updated." ) - if formatted: - expected = [ - { - "charm_name": "testcharm", - "library_name": "testlib", - "library_id": lib_id, - "api": 0, - "error_message": error_message, - }, - ] - emitter.assert_json_output(expected) - else: - emitter.assert_message(error_message) # endregion From dc917b1bcb960c61289361dd4bbe81f9759b3275 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:51:56 +0000 Subject: [PATCH 07/51] build(deps): update bugfixes --- .pre-commit-config.yaml | 2 +- requirements-dev.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 199feb983..f8f67056c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: fix-byte-order-marker - id: mixed-line-ending - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.9.1" + rev: "v0.9.2" hooks: # Run the linter - id: ruff diff --git a/requirements-dev.txt b/requirements-dev.txt index d135a0fdd..e50fadaf4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.4.1 coverage==7.6.10 -craft-application==4.8.1 +craft-application==4.8.2 craft-archives==2.0.2 craft-cli==2.13.0 craft-grammar==2.0.1 @@ -59,7 +59,7 @@ protobuf==5.29.3 pycparser==2.22 pydantic==2.9.2 pydantic-core==2.23.4 -pyfakefs==5.7.3 +pyfakefs==5.7.4 pygit2==1.14.1 pylint==3.3.3 pymacaroons==0.13.0 From 7669dea44ff8505b87f03e3dd03c6643f000a1f8 Mon Sep 17 00:00:00 2001 From: erinecon Date: Tue, 21 Jan 2025 09:02:27 -0500 Subject: [PATCH 08/51] docs: remove experimental extension flag --- docs/tutorial/code/django/task.yaml | 16 ++++++++-------- ...irst-kubernetes-charm-for-a-django-app.rst | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index d60c49438..6a15bfbe9 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -74,7 +74,7 @@ execute: | cat $HOME/settings_init_rock.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py # [docs:pack] - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft pack # [docs:pack-end] # [docs:skopeo-copy] @@ -96,7 +96,7 @@ execute: | cat $HOME/postgres_requires_charmcraft.yaml >> charmcraft.yaml # [docs:charm-pack] - CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + charmcraft pack # [docs:charm-pack-end] # [docs:add-juju-model] @@ -169,7 +169,7 @@ execute: | rockcraft pack # [docs:repack-update] - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:django-hello-world_0.2_$(dpkg --print-architecture).rock \ docker://localhost:32000/django-hello-world:0.2 @@ -198,10 +198,10 @@ execute: | sed -i "s/version: .*/version: 0.3/g" rockcraft.yaml # [docs:repack-2nd-update] - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack - rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.3_$(dpkg --print-architecture).rock \ - docker://localhost:32000/django-hello-world:0.3 + rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:django-hello-world_0.3_$(dpkg --print-architecture).rock \ + docker://localhost:32000/django-hello-world:0.3 # [docs:repack-2nd-update-end] cd charm @@ -210,7 +210,7 @@ execute: | cat $HOME/greeting_charmcraft.yaml >> ./charmcraft.yaml # [docs:repack-refresh-2nd-deployment] - CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + charmcraft pack juju refresh django-hello-world \ --path=./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ --resource django-app-image=localhost:32000/django-hello-world:0.3 diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index a2a94a166..4bd5e911c 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -273,12 +273,12 @@ Now let’s pack the rock: .. code:: bash - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft pack .. note:: - ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required whilst the Django - extension is experimental. + ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true`` may be required + for the packing for older versions of Rockcraft. Depending on your network, this step can take a couple of minutes to finish. Once Rockcraft has finished packing the Django rock, the @@ -345,7 +345,12 @@ Now let’s pack the charm: .. code:: bash - CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + charmcraft pack + +.. note:: + + ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true`` may be required + in the pack command for older versions of Charmcraft. Depending on your system and network, this step can take a couple of minutes to finish. @@ -577,7 +582,7 @@ Now let’s pack and upload the rock using similar commands as before: .. code:: bash - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:django-hello-world_0.2_amd64.rock \ docker://localhost:32000/django-hello-world:0.2 @@ -660,7 +665,7 @@ Let’s run the pack and upload commands for the rock: .. code:: bash - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:django-hello-world_0.3_amd64.rock \ docker://localhost:32000/django-hello-world:0.3 @@ -692,7 +697,7 @@ We can now pack and deploy the new version of the Django app: .. code:: bash - CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + charmcraft pack juju refresh django-hello-world \ --path=./django-hello-world_ubuntu-22.04-amd64.charm \ --resource django-app-image=localhost:32000/django-hello-world:0.3 From f3fd4b7760ef935d83cfe285b6ddd19438a156d0 Mon Sep 17 00:00:00 2001 From: erinecon Date: Tue, 21 Jan 2025 09:16:27 -0500 Subject: [PATCH 09/51] docs: add reuse setup file --- docs/reuse/tutorial/setup_edge.rst | 85 +++++++++++++++++++ docs/tutorial/code/django/task.yaml | 7 +- ...irst-kubernetes-charm-for-a-django-app.rst | 12 +-- 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 docs/reuse/tutorial/setup_edge.rst diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst new file mode 100644 index 000000000..66f2608cf --- /dev/null +++ b/docs/reuse/tutorial/setup_edge.rst @@ -0,0 +1,85 @@ +First, `install Multipass `_. + +Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` +from the 24.04 blueprint: + +.. code-block:: bash + + multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 24.04 + +Once the VM is up, open a shell into it: + +.. code-block:: bash + + multipass shell charm-dev + +In order to create the rock, you'll need to install Rockcraft with the +``--classic`` confinement to allow access to the whole file system: + +.. code-block:: bash + + sudo snap install rockcraft --classic + + +``LXD`` will be required for building the rock. Make sure it is installed +and initialised: + +.. code-block:: bash + + lxd --version + lxd init --auto + + +If ``LXD`` is not installed, install it with ``sudo snap install lxd``. + +In order to create the charm, you'll need to install Charmcraft: + +.. code-block:: bash + + sudo snap install charmcraft --channel latest/edge --classic + +.. warning:: + + This tutorial requires version ``3.2.0`` or later of Charmcraft. + Check the version of Charmcraft using ``charmcraft --version``. + +MicroK8s is required to deploy the Django application on Kubernetes. +Let's install MicroK8s using the ``1.31-strict/stable`` track: + +.. code-block:: bash + + sudo snap install microk8s --channel 1.31-strict/stable + sudo adduser $USER snap_microk8s + newgrp snap_microk8s + +Wait for MicroK8s to be ready: + +.. code-block:: bash + + sudo microk8s status --wait-ready + +Several MicroK8s add-ons are required for deployment: + +.. code-block:: bash + + # Required for Juju to provide storage volumes + sudo microk8s enable hostpath-storage + # Required to host the OCI image of the Django application + sudo microk8s enable registry + # Required to expose the Django application + sudo microk8s enable ingress + +Juju is required to deploy the Django application. +Install Juju using the ``3.5/stable`` track, and bootstrap a +development controller: + +.. code-block:: bash + + sudo snap install juju --channel 3.5/stable + mkdir -p ~/.local/share + juju bootstrap microk8s dev-controller + +.. note:: + + It could take a few minutes to download the images. + diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 6a15bfbe9..4bc589f4c 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -47,10 +47,15 @@ execute: | sudo apt-get update && sudo apt-get install python3-venv -y python3 -m venv .venv source .venv/bin/activate - pip install -r requirements.txt # [docs:create-venv-end] + # [docs:install-requirements] + pip install -r requirements.txt + # [docs:install-requirements-end] + + # [docs:django-startproject] django-admin startproject django_hello_world + # [docs:django-startproject-end] # Update settings.py file cat $HOME/settings_local_run.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 4bd5e911c..e0b16e30a 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -43,7 +43,7 @@ times, mimicking a real development process. Set things up ------------- -.. include:: setup_edge.rst +.. include:: /reuse/tutorial/setup_edge.rst Let’s create a new directory for this tutorial and change into it: @@ -54,11 +54,11 @@ Let’s create a new directory for this tutorial and change into it: Finally, install ``python3-venv`` and create a virtual environment: -.. code:: bash - - sudo apt-get update && sudo apt-get install python3-venv -y - python3 -m venv .venv - source .venv/bin/activate +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:create-venv] + :end-before: [docs:create-venv-end] + :dedent: 2 Create the Django application ----------------------------- From 92ac018e061b35d3a3f91e6c7810b34d75eeaeb9 Mon Sep 17 00:00:00 2001 From: erinecon Date: Tue, 21 Jan 2025 10:53:34 -0500 Subject: [PATCH 10/51] docs: replace code with wrapped commands --- docs/tutorial/code/django/requirements.txt | 2 + docs/tutorial/code/django/task.yaml | 23 +- ...irst-kubernetes-charm-for-a-django-app.rst | 277 +++++++++--------- 3 files changed, 156 insertions(+), 146 deletions(-) create mode 100644 docs/tutorial/code/django/requirements.txt diff --git a/docs/tutorial/code/django/requirements.txt b/docs/tutorial/code/django/requirements.txt new file mode 100644 index 000000000..fb22bd75c --- /dev/null +++ b/docs/tutorial/code/django/requirements.txt @@ -0,0 +1,2 @@ +Django +psycopg2-binary diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 4bc589f4c..67868b5fc 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -60,8 +60,11 @@ execute: | # Update settings.py file cat $HOME/settings_local_run.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py - # Run and test Django app + # [docs:run-django-app] python3 manage.py runserver 0.0.0.0:8000 & + # [docs:run-django-app-end] + + # Test the Django app retry -n 5 --wait 2 curl --fail localhost:8000 curl localhost:8000 | grep Congratulations @@ -108,13 +111,15 @@ execute: | juju add-model django-hello-world # [docs:add-juju-model-end] + # [docs:add-model-constraints] juju set-model-constraints -m django-hello-world arch=$(dpkg --print-architecture) + # [docs:add-model-constraints-end] - # [docs:deploy-juju-model] + # [docs:deploy-django-app] juju deploy ./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ django-hello-world --resource \ django-app-image=localhost:32000/django-hello-world:0.1 - # [docs:deploy-juju-model-end] + # [docs:deploy-django-app-end] # [docs:deploy-postgres] juju deploy postgresql-k8s --trust @@ -128,11 +133,12 @@ execute: | juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m juju wait-for application django-hello-world --query='status=="active"' --timeout 10m - # Configure allowed-hosts and debug mode + # [docs:config-allowed-hosts-debug] juju config django-hello-world django-allowed-hosts=* django-debug=true + # [docs:config-allowed-hosts-debug-end] # [docs:deploy-nginx] - juju deploy nginx-ingress-integrator --channel=latest/edge --base ubuntu@20.04 + juju deploy nginx-ingress-integrator --channel=latest/stable --trust juju integrate nginx-ingress-integrator django-hello-world # [docs:deploy-nginx-end] @@ -148,8 +154,9 @@ execute: | # curl the Django app curl http://django-hello-world --resolve django-hello-world:80:127.0.0.1 | grep Congratulations - # Configure allowed-hosts + # [docs:config-allowed-hosts] juju config django-hello-world django-allowed-hosts=django-hello-world + # [docs:config-allowed-hosts-end] # curl again curl http://django-hello-world --resolve django-hello-world:80:127.0.0.1 | grep Congratulations @@ -171,7 +178,6 @@ execute: | cat $HOME/urls_django_hello_world.py > django_hello_world/urls.py sed -i "s/version: .*/version: 0.2/g" rockcraft.yaml - rockcraft pack # [docs:repack-update] rockcraft pack @@ -187,8 +193,9 @@ execute: | --resource django-app-image=localhost:32000/django-hello-world:0.2 # [docs:refresh-deployment-end] - # Disable debug mode + # [docs:disable-debug-mode] juju config django-hello-world django-debug=false + # [docs:disable-debug-mode-end] # give Juju some time to refresh the app juju wait-for application django-hello-world --query='status=="active"' --timeout 10m diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index e0b16e30a..ac8612059 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -66,10 +66,7 @@ Create the Django application Create a ``requirements.txt`` file, copy the following text into it and then save it: -.. code:: bash - - Django - psycopg2-binary +.. literalinclude:: code/django/requirements.txt .. note:: @@ -78,15 +75,19 @@ then save it: Install the packages: -.. code:: bash - - pip install -r requirements.txt +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:install-requirements] + :end-before: [docs:install-requirements-end] + :dedent: 2 Create a new project using ``django-admin``: -.. code:: bash - - django-admin startproject django_hello_world +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:django-startproject] + :end-before: [docs:django-startproject-end] + :dedent: 2 Run the Django application locally ---------------------------------- @@ -112,9 +113,11 @@ Save and close the ``settings.py`` file. Now, run the Django application to verify that it works: -.. code:: bash - - python3 manage.py runserver 0.0.0.0:8000 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:run-django-app] + :end-before: [docs:run-django-app-end] + :dedent: 2 .. note:: @@ -271,14 +274,16 @@ Save and close the ``settings.py`` file. Now let’s pack the rock: -.. code:: bash - - rockcraft pack +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:pack] + :end-before: [docs:pack-end] + :dedent: 2 .. note:: ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true`` may be required - for the packing for older versions of Rockcraft. + in the pack command for older versions of Rockcraft. Depending on your network, this step can take a couple of minutes to finish. Once Rockcraft has finished packing the Django rock, the @@ -294,11 +299,11 @@ The rock needs to be copied to the MicroK8s registry, which stores OCI archives so they can be downloaded and deployed in a Kubernetes cluster. Copy the rock: -.. code:: bash - - rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.1_amd64.rock \ - docker://localhost:32000/django-hello-world:0.1 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:skopeo-copy] + :end-before: [docs:skopeo-copy-end] + :dedent: 2 .. seealso:: @@ -310,10 +315,11 @@ Create the charm From the ``django-hello-world`` directory, create a new directory for the charm and change inside it: -.. code:: bash - - mkdir charm - cd charm +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:create-charm-dir] + :end-before: [docs:create-charm-dir-end] + :dedent: 2 Using the ``django-framework`` profile, Charmcraft will automate the creation of the files needed for our charm, including a @@ -323,9 +329,11 @@ application. Initialize a charm named ``django-hello-world``: -.. code:: bash - - charmcraft init --profile django-framework --name django-hello-world +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:charm-init] + :end-before: [docs:charm-init-end] + :dedent: 2 The files will automatically be created in your working directory. @@ -333,19 +341,16 @@ We will need to connect to the PostgreSQL database. Open the ``charmcraft.yaml`` file and add the following section to the end of the file: -.. code:: yaml - - requires: - postgresql: - interface: postgresql_client - optional: false - limit: 1 +.. literalinclude:: code/django/postgres_requires_charmcraft.yaml + :language: yaml Now let’s pack the charm: -.. code:: bash - - charmcraft pack +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:charm-pack] + :end-before: [docs:charm-pack-end] + :dedent: 2 .. note:: @@ -370,42 +375,50 @@ Deploy the Django application A Juju model is needed to handle Kubernetes resources while deploying the Django application. Let’s create a new model: -.. code:: bash - - juju add-model django-hello-world +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:add-juju-model] + :end-before: [docs:add-juju-model-end] + :dedent: 2 If you are not on a host with the ``amd64`` architecture, you will need to include a constraint to the Juju model to specify your architecture. Check the architecture of your system using ``dpkg --print-architecture``. -For the ``arm64`` architecture, set the model constraints using - -.. code:: bash +Set the Juju model constraints using - juju set-model-constraints -m django-hello-world arch=arm64 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:add-model-constraints] + :end-before: [docs:add-model-constraints-end] + :dedent: 2 Now let’s use the OCI image we previously uploaded to deploy the Django application. Deploy using Juju by specifying the OCI image name with the ``--resource`` option: -.. code:: bash - - juju deploy ./django-hello-world_ubuntu-22.04-amd64.charm \ - django-hello-world \ - --resource django-app-image=localhost:32000/django-hello-world:0.1 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:deploy-django-app] + :end-before: [docs:deploy-django-app-end] + :dedent: 2 Now let’s deploy PostgreSQL: -.. code:: bash - - juju deploy postgresql-k8s --trust +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:deploy-postgres] + :end-before: [docs:deploy-postgres-end] + :dedent: 2 Integrate PostgreSQL with the Django application: -.. code:: bash - - juju integrate django-hello-world postgresql-k8s +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:integrate-postgres] + :end-before: [docs:integrate-postgres-end] + :dedent: 2 It will take a few minutes to deploy the Django application. You can monitor the progress using @@ -442,9 +455,11 @@ To be able to test the deployment, we need to include the IP address in the allowed hosts configuration. We’ll also enable debug mode for now whilst we are testing. Set both configurations: -.. code:: bash - - juju config django-hello-world django-allowed-hosts=* django-debug=true +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:config-allowed-hosts-debug] + :end-before: [docs:config-allowed-hosts-debug-end] + :dedent: 2 .. note:: @@ -456,18 +471,20 @@ whilst we are testing. Set both configurations: Let’s expose the application using ingress. Deploy the ``nginx-ingress-integrator`` charm and integrate it with the Django app: -.. code:: bash - - juju deploy nginx-ingress-integrator - juju integrate nginx-ingress-integrator django-hello-world +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:deploy-nginx] + :end-before: [docs:deploy-nginx-end] + :dedent: 2 The hostname of the app needs to be defined so that it is accessible via the ingress. We will also set the default route to be the root endpoint: -.. code:: bash - - juju config nginx-ingress-integrator \ - service-hostname=django-hello-world path-routes=/ +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:config-nginx] + :end-before: [docs:config-nginx-end] + :dedent: 2 Monitor ``juju status`` until everything has a status of ``active``. @@ -491,9 +508,11 @@ We can now also change the Django allowed hosts to production, you will need to set up a DNS record). Inside the Multipass VM, set the configuration: -.. code:: bash - - juju config django-hello-world django-allowed-hosts=django-hello-world +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:config-allowed-hosts] + :end-before: [docs:config-allowed-hosts-end] + :dedent: 2 Visiting http://django-hello-world should still respond with ``The install worked successfully! Congratulations!``. @@ -508,31 +527,21 @@ out to the ``django-hello-world`` directory where the rock is and enter into the ``django_hello_world`` directory where the Django application is. Let’s add a new Django app: -.. code:: bash - - django-admin startapp greeting +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:startapp-greeting] + :end-before: [docs:startapp-greeting-end] + :dedent: 2 Open the ``greeting/views.py`` file and replace the content with: -.. code:: python - - from django.http import HttpResponse - - - def index(request): - return HttpResponse("Hello, world!\n") +.. literalinclude:: code/django/views_greeting.py + :language: python Create the ``greeting/urls.py`` file with the following contents: -.. code:: python - - from django.urls import path - - from . import views - - urlpatterns = [ - path("", views.index, name="index"), - ] +.. literalinclude:: code/django/urls_greeting.py + :language: python Open the ``django_hello_world/urls.py`` file and edit the imports to contain ``include`` and the value of ``urlpatterns`` to include @@ -580,28 +589,28 @@ the ``rockcraft.yaml`` file should look similar to the following: Now let’s pack and upload the rock using similar commands as before: -.. code:: bash - - rockcraft pack - rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.2_amd64.rock \ - docker://localhost:32000/django-hello-world:0.2 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:repack-update] + :end-before: [docs:repack-update-end] + :dedent: 2 Now we can deploy the new version of the Django application from the ``charm`` directory using: -.. code:: bash - - cd charm - juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/django-hello-world:0.2 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:refresh-deployment] + :end-before: [docs:refresh-deployment-end] + :dedent: 2 Now that we have the greeting app, we can disable debug mode: -.. code:: bash - - juju config django-hello-world django-debug=false +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:disable-debug-mode] + :end-before: [docs:disable-debug-mode-end] + :dedent: 2 Use ``juju status --watch 5s`` again to wait until the App is active again. You may visit http://django-hello-world from a web browser, or @@ -624,15 +633,8 @@ directory ``django-hello-world`` using ``cd ..``. From there, open the ``django_hello_world/greeting/views.py`` file and replace the content with: -.. code:: python - - import os - - from django.http import HttpResponse - - - def index(request): - return HttpResponse(f"{os.environ.get('DJANGO_GREETING', 'Hello, world!')}\n") +.. literalinclude:: code/django/views_greeting_configuration.py + :language: python Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the top of the ``rockcraft.yaml`` file looks similar to the following: @@ -663,12 +665,11 @@ top of the ``rockcraft.yaml`` file looks similar to the following: Let’s run the pack and upload commands for the rock: -.. code:: bash - - rockcraft pack - rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:django-hello-world_0.3_amd64.rock \ - docker://localhost:32000/django-hello-world:0.3 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:repack-2nd-update] + :end-before: [docs:repack-2nd-update-end] + :dedent: 2 Change back into the charm directory using ``cd charm``. @@ -677,15 +678,8 @@ configurations in ``charmcraft.yaml`` which will be passed as environment variables to the Django application. Add the following to the end of the ``charmcraft.yaml`` file: -.. code:: yaml - - config: - options: - greeting: - description: | - The greeting to be returned by the Django application. - default: "Hello, world!" - type: string +.. literalinclude:: code/django/greeting_charmcraft.yaml + :language: yaml .. note:: @@ -695,12 +689,11 @@ the end of the ``charmcraft.yaml`` file: We can now pack and deploy the new version of the Django app: -.. code:: bash - - charmcraft pack - juju refresh django-hello-world \ - --path=./django-hello-world_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/django-hello-world:0.3 +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:repack-refresh-2nd-deployment] + :end-before: [docs:repack-refresh-2nd-deployment-end] + :dedent: 2 After we wait for a bit monitoring ``juju status`` the application should go back to ``active`` again. Sending a request to the root @@ -710,9 +703,11 @@ Django application responding with ``Hello, world!`` again. Now let’s change the greeting: -.. code:: bash - - juju config django-hello-world greeting='Hi!' +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:change-config] + :end-before: [docs:change-config-end] + :dedent: 2 After we wait for a moment for the app to be restarted, using ``curl 127.0.0.1 -H "Host: django-hello-world"`` or visiting @@ -736,6 +731,12 @@ development process, including: If you’d like to reset your working environment, you can run the following in the rock directory ``django-hello-world`` for the tutorial: +.. literalinclude:: code/django/task.yaml + :language: bash + :start-after: [docs:clean-environment] + :end-before: [docs:clean-environment-end] + :dedent: 2 + .. code:: bash deactivate From 659b28d8b9d1d19d7ff38bfeed04f189bef7796c Mon Sep 17 00:00:00 2001 From: erinecon Date: Tue, 21 Jan 2025 14:45:04 -0500 Subject: [PATCH 11/51] fix: remove duplicate content, break commands up --- docs/tutorial/code/django/task.yaml | 12 ++++++++---- ...your-first-kubernetes-charm-for-a-django-app.rst | 13 ------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 67868b5fc..158eb5c2c 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -112,11 +112,13 @@ execute: | # [docs:add-juju-model-end] # [docs:add-model-constraints] - juju set-model-constraints -m django-hello-world arch=$(dpkg --print-architecture) + juju set-model-constraints -m django-hello-world \ + arch=$(dpkg --print-architecture) # [docs:add-model-constraints-end] # [docs:deploy-django-app] - juju deploy ./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + juju deploy \ + ./django-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ django-hello-world --resource \ django-app-image=localhost:32000/django-hello-world:0.1 # [docs:deploy-django-app-end] @@ -251,9 +253,11 @@ execute: | # [docs:clean-environment] # exit and delete the virtual environment deactivate - rm -rf charm .venv __pycache__ django_hello_world + rm -rf .venv # delete all the files created during the tutorial - rm django-hello-world_0.1_$(dpkg --print-architecture).rock django-hello-world_0.2_$(dpkg --print-architecture).rock \ + rm -rf charm __pycache__ django_hello_world + rm django-hello-world_0.1_$(dpkg --print-architecture).rock \ + django-hello-world_0.2_$(dpkg --print-architecture).rock \ django-hello-world_0.3_$(dpkg --print-architecture).rock \ rockcraft.yaml requirements.txt # Remove the juju model diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index ac8612059..5c4756178 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -737,19 +737,6 @@ following in the rock directory ``django-hello-world`` for the tutorial: :end-before: [docs:clean-environment-end] :dedent: 2 -.. code:: bash - - deactivate - rm -rf charm .venv django_hello_world - # delete all the files created during the tutorial - rm django-hello-world_0.1_amd64.rock \ - django-hello-world_0.2_amd64.rock \ - django-hello-world_0.3_amd64.rock \ - rockcraft.yaml requirements.txt - - # Remove the Juju model - juju destroy-model django-hello-world --destroy-storage - You can also clean up your Multipass instance. Start by exiting it: .. code:: bash From d28c88e9e6cbd3031b68babcd78937db72855606 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 22 Jan 2025 10:51:07 -0500 Subject: [PATCH 12/51] fix: incorporate changes from Design team --- ...irst-kubernetes-charm-for-a-django-app.rst | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 5c4756178..9aebeae33 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -123,17 +123,17 @@ Now, run the Django application to verify that it works: Specifying ``0.0.0.0:8000`` allows for traffic outside of the Multipass VM. -Now we need the private IP address of the Multipass VM. Use one of the -following options for finding the IP address: - -1. Open a new terminal of the Multipass VM using - ``multipass shell charm-dev`` and run - ``ip route get $(ip route show 0.0.0.0/0 | grep -oP 'via \K\S+') | grep -oP 'src \K\S+'``. -2. Outside of the Multipass VM, run - ``multipass info charm-dev | grep IP``. The ``grep`` command extracts - a portion of the output to highlight the IP address. -3. In the Multipass GUI, under the Instances tab, find the private IP - address listed under the ``charm-dev`` VM. +Now we need the private IP address of the Multipass VM. Outside of the +Multipass VM, run: + +.. code-block:: + + multipass info charm-dev | grep IP + +.. note:: + + The ``grep`` command extracts a portion of the output to highlight the + IP address. With the Multipass IP address, we can visit the Django app in a web browser. Open a new tab and visit @@ -425,8 +425,9 @@ monitor the progress using .. code:: bash - juju status --watch 5s + juju status --relations --watch 2s +The ``--relations`` flag will list the currently enabled integrations. It can take a couple of minutes for the apps to finish the deployment. During this time, the Django app may enter a ``blocked`` state as it waits to become integrated with the PostgreSQL database. @@ -612,7 +613,7 @@ Now that we have the greeting app, we can disable debug mode: :end-before: [docs:disable-debug-mode-end] :dedent: 2 -Use ``juju status --watch 5s`` again to wait until the App is active +Use ``juju status --watch 2s`` again to wait until the App is active again. You may visit http://django-hello-world from a web browser, or you can use ``curl 127.0.0.1 -H "Host: django-hello-world"`` inside the Multipass VM. Either way, the Django application should respond with From 5514ed9335799634217d8db271e532ff7d35cc83 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 22 Jan 2025 11:19:39 -0500 Subject: [PATCH 13/51] fix: revert command to run app locally --- ...write-your-first-kubernetes-charm-for-a-django-app.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 9aebeae33..278154e71 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -113,11 +113,9 @@ Save and close the ``settings.py`` file. Now, run the Django application to verify that it works: -.. literalinclude:: code/django/task.yaml - :language: bash - :start-after: [docs:run-django-app] - :end-before: [docs:run-django-app-end] - :dedent: 2 +.. code:: bash + + python3 manage.py runserver 0.0.0.0:8000 .. note:: From 4b4a6848d5083cbadd2266fea2166347b0b86da3 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 22 Jan 2025 14:57:59 -0500 Subject: [PATCH 14/51] fix: update Django spread test --- docs/tutorial/code/django/task.yaml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 158eb5c2c..184f31959 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -28,7 +28,7 @@ execute: | snap install lxd lxd init --auto snap install microk8s --channel=1.31-strict/stable - snap refresh juju --channel=3.5/stable --amend + snap install juju --channel=3.5/stable # Juju config setup lxc network set lxdbr0 ipv6.address none @@ -43,12 +43,17 @@ execute: | # Bootstrap controller juju bootstrap microk8s dev-controller + # Create working dir and cd + mkdir django-hello-world + cd django-hello-world + # [docs:create-venv] sudo apt-get update && sudo apt-get install python3-venv -y python3 -m venv .venv source .venv/bin/activate # [docs:create-venv-end] + mv $HOME/requirements.txt $HOME/django-hello-world/ # [docs:install-requirements] pip install -r requirements.txt # [docs:install-requirements-end] @@ -57,12 +62,14 @@ execute: | django-admin startproject django_hello_world # [docs:django-startproject-end] + # cd into django project dir + cd django_hello_world + # Update settings.py file cat $HOME/settings_local_run.py > $HOME/django-hello-world/django_hello_world/django_hello_world/settings.py - # [docs:run-django-app] + # Run Django app locally python3 manage.py runserver 0.0.0.0:8000 & - # [docs:run-django-app-end] # Test the Django app retry -n 5 --wait 2 curl --fail localhost:8000 @@ -70,6 +77,7 @@ execute: | curl localhost:8000 | grep Congratulations kill $! + cd $HOME/django-hello-world # [docs:create-rockcraft-yaml] rockcraft init --profile django-framework @@ -179,6 +187,7 @@ execute: | # Update django_hello_world/urls.py file cat $HOME/urls_django_hello_world.py > django_hello_world/urls.py + cd .. sed -i "s/version: .*/version: 0.2/g" rockcraft.yaml # [docs:repack-update] @@ -200,6 +209,7 @@ execute: | # [docs:disable-debug-mode-end] # give Juju some time to refresh the app + juju wait-for application django-hello-world --query='status=="maintenance"' --timeout 10m juju wait-for application django-hello-world --query='status=="active"' --timeout 10m # curl and check that the response is Hello, world! From 9046167702b9c340a4ee069bb077ee74facc20cc Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 09:30:06 -0500 Subject: [PATCH 15/51] docs: update Django pack warning --- .../write-your-first-kubernetes-charm-for-a-django-app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 278154e71..556428828 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -283,7 +283,7 @@ Now let’s pack the rock: ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true`` may be required in the pack command for older versions of Rockcraft. -Depending on your network, this step can take a couple of minutes to +Depending on your system and network, this step can take several minutes to finish. Once Rockcraft has finished packing the Django rock, the terminal will respond with something similar to ``Packed django-hello-world_0.1_amd64.rock``. From bdc53c2b7411112f4357b64d6f6a56e17493ab19 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 09:32:14 -0500 Subject: [PATCH 16/51] docs: update Django charm pack warning --- .../write-your-first-kubernetes-charm-for-a-django-app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 556428828..8a4066583 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -355,7 +355,7 @@ Now let’s pack the charm: ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true`` may be required in the pack command for older versions of Charmcraft. -Depending on your system and network, this step can take a couple of +Depending on your system and network, this step can take several minutes to finish. Once Charmcraft has finished packing the charm, the terminal will From ecd8773e8ca71c277f64b2b194ca907e1f5202f1 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 11:18:41 -0500 Subject: [PATCH 17/51] fix: update Flask task file --- docs/tutorial/code/flask/task.yaml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 13c5504e1..ac0da5ca6 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -28,7 +28,7 @@ execute: | snap install lxd lxd init --auto snap install microk8s --channel=1.31-strict/stable - snap refresh juju --channel=3.5/stable --amend + snap install juju --channel=3.5/stable # Juju config setup lxc network set lxdbr0 ipv6.address none @@ -104,13 +104,14 @@ execute: | juju set-model-constraints -m flask-hello-world arch=$(dpkg --print-architecture) # [docs:deploy-juju-model] - juju deploy ./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ + juju deploy \ + ./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ flask-hello-world --resource \ flask-app-image=localhost:32000/flask-hello-world:0.1 # [docs:deploy-juju-model-end] # [docs:deploy-nginx] - juju deploy nginx-ingress-integrator --channel=latest/edge --base ubuntu@20.04 + juju deploy nginx-ingress-integrator --channel=latest/stable --trust juju integrate nginx-ingress-integrator flask-hello-world # [docs:deploy-nginx-end] @@ -189,7 +190,7 @@ execute: | # [docs:refresh-2nd-deployment-end] # [docs:deploy-postgres] - juju deploy postgresql-k8s --channel=14/stable --trust + juju deploy postgresql-k8s --trust juju integrate flask-hello-world postgresql-k8s # [docs:deploy-postgres-end] @@ -204,7 +205,7 @@ execute: | curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 | grep Hi curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 | grep 2 - # Back out to main directory for clean-up + # Back out to main directory for cleanup cd .. # [docs:clean-environment] @@ -212,9 +213,10 @@ execute: | deactivate rm -rf charm .venv __pycache__ # delete all the files created during the tutorial - rm flask-hello-world_0.1_$(dpkg --print-architecture).rock flask-hello-world_0.2_$(dpkg --print-architecture).rock \ - flask-hello-world_0.3_$(dpkg --print-architecture).rock rockcraft.yaml app.py \ - requirements.txt migrate.py + rm flask-hello-world_0.1_$(dpkg --print-architecture).rock \ + flask-hello-world_0.2_$(dpkg --print-architecture).rock \ + flask-hello-world_0.3_$(dpkg --print-architecture).rock \ + rockcraft.yaml app.py requirements.txt migrate.py # Remove the juju model juju destroy-model flask-hello-world --destroy-storage --no-prompt --force # [docs:clean-environment-end] From 2991bf5633d61ac930f98b5f9e848aa2e5c42da1 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 14:13:55 -0500 Subject: [PATCH 18/51] docs: update Flask tutorial files --- docs/tutorial/code/flask/requirements.txt | 1 + docs/tutorial/code/flask/task.yaml | 24 +- docs/tutorial/flask.rst | 403 +++++++++++------- ...irst-kubernetes-charm-for-a-django-app.rst | 42 +- 4 files changed, 293 insertions(+), 177 deletions(-) diff --git a/docs/tutorial/code/flask/requirements.txt b/docs/tutorial/code/flask/requirements.txt index e3e9a71d9..630d4b819 100644 --- a/docs/tutorial/code/flask/requirements.txt +++ b/docs/tutorial/code/flask/requirements.txt @@ -1 +1,2 @@ Flask +psycopg2-binary diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index ac0da5ca6..3c45dbdf6 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -47,9 +47,12 @@ execute: | sudo apt-get update && sudo apt-get install python3-venv -y python3 -m venv .venv source .venv/bin/activate - pip install -r requirements.txt # [docs:create-venv-end] + # [docs:install-requirements] + pip install -r requirements.txt + # [docs:install-requirements-end] + flask run -p 8000 & retry -n 5 --wait 2 curl --fail localhost:8000 @@ -70,10 +73,6 @@ execute: | rockcraft pack # [docs:pack-end] - # [docs:ls-rock] - ls *.rock -l - # [docs:ls-rock-end] - # [docs:skopeo-copy] rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:flask-hello-world_0.1_$(dpkg --print-architecture).rock \ @@ -101,14 +100,16 @@ execute: | juju add-model flask-hello-world # [docs:add-juju-model-end] + #[docs:add-model-constraints] juju set-model-constraints -m flask-hello-world arch=$(dpkg --print-architecture) + #[docs:add-model-constraints-end] - # [docs:deploy-juju-model] + # [docs:deploy-flask-app] juju deploy \ ./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ flask-hello-world --resource \ flask-app-image=localhost:32000/flask-hello-world:0.1 - # [docs:deploy-juju-model-end] + # [docs:deploy-flask-app-end] # [docs:deploy-nginx] juju deploy nginx-ingress-integrator --channel=latest/stable --trust @@ -131,9 +132,9 @@ execute: | cd .. cat greeting_app.py > app.py sed -i "s/version: .*/version: 0.2/g" rockcraft.yaml - rockcraft pack # [docs:docker-update] + rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:flask-hello-world_0.2_$(dpkg --print-architecture).rock \ docker://localhost:32000/flask-hello-world:0.2 @@ -141,9 +142,9 @@ execute: | cat greeting_charmcraft.yaml >> ./charm/charmcraft.yaml cd charm - charmcraft pack # [docs:refresh-deployment] + charmcraft pack juju refresh flask-hello-world \ --path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ --resource flask-app-image=localhost:32000/flask-hello-world:0.2 @@ -170,10 +171,9 @@ execute: | cat visitors_migrate.py >> migrate.py cat visitors_app.py > app.py sed -i "s/version: .*/version: 0.3/g" rockcraft.yaml - echo "psycopg2-binary" >> requirements.txt - rockcraft pack # [docs:docker-2nd-update] + rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:flask-hello-world_0.3_$(dpkg --print-architecture).rock \ docker://localhost:32000/flask-hello-world:0.3 @@ -181,9 +181,9 @@ execute: | cat visitors_charmcraft.yaml >> ./charm/charmcraft.yaml cd charm - charmcraft pack # [docs:refresh-2nd-deployment] + charmcraft pack juju refresh flask-hello-world \ --path=./flask-hello-world_ubuntu-22.04-$(dpkg --print-architecture).charm \ --resource flask-app-image=localhost:32000/flask-hello-world:0.3 diff --git a/docs/tutorial/flask.rst b/docs/tutorial/flask.rst index 77769b40e..3ee6242a1 100644 --- a/docs/tutorial/flask.rst +++ b/docs/tutorial/flask.rst @@ -23,7 +23,7 @@ This tutorial should take 90 minutes for you to complete. cluster with the Juju orchestration engine. What you'll need -================ +---------------- - A workstation, e.g., a laptop, with amd64 or arm64 architecture which has sufficient resources to launch a virtual machine with 4 CPUs, @@ -31,33 +31,36 @@ What you'll need - Familiarity with Linux What you'll do -============== - -- Set things up -- Create the Flask application -- Run the Flask application locally -- Pack the Flask application into a rock called ``flask-hello-world`` -- Create the charm called ``flask-hello-world`` -- Deploy the Flask application and expose via ingress -- Enable ``juju config flask-hello-world greeting=`` -- Integrate with a database -- Clean up environment +-------------- + +Create a Flask application. Use that to create a rock with +``rockcraft``. Use that to create a charm with ``charmcraft``. Use that +to test-deploy, configure, etc., your Flask application on a local +Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple +times, mimicking a real development process. Set things up -============= +------------- .. include:: /reuse/tutorial/setup_stable.rst -Finally, let's create a new directory for this tutorial and go -inside it: +Let's create a new directory for this tutorial and change into it: .. code-block:: bash mkdir flask-hello-world cd flask-hello-world +Finally, install ``python3-venv`` and create a virtual environment: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:create-venv] + :end-before: [docs:create-venv-end] + :dedent: 2 + Create the Flask application -============================ +---------------------------- Let's start by creating the "Hello, world" Flask application that will be used for this tutorial. @@ -67,6 +70,19 @@ and then save it: .. literalinclude:: code/flask/requirements.txt +.. note:: + + The ``psycopg2-binary`` package is needed so the Flask application can + connect to PostgreSQL. + +Install the packages: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:install-requirements] + :end-before: [docs:install-requirements-end] + :dedent: 2 + In the same directory, copy and save the following into a text file called ``app.py``: @@ -74,15 +90,7 @@ called ``app.py``: :language: python Run the Flask application locally -================================= - -Let's install ``python3-venv`` and create a virtual environment: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:create-venv] - :end-before: [docs:create-venv-end] - :dedent: 2 +--------------------------------- Now that we have a virtual environment with all the dependencies, let's run the Flask application to verify that it works: @@ -101,16 +109,18 @@ endpoint. You will need a new terminal for this; use :end-before: [docs:curl-flask-end] :dedent: 2 -The Flask application should respond with ``Hello, world!``. The Flask -application looks good, so we can stop for now using +The Flask application should respond with ``Hello, world!``. + +The Flask application looks good, so we can stop for now using :kbd:`Ctrl` + :kbd:`C`. Pack the Flask application into a rock -====================================== +-------------------------------------- -First, we'll need a ``rockcraft.yaml`` file. Rockcraft will automate its -creation and tailoring for a Flask application by using the -``flask-framework`` profile: +First, we'll need a ``rockcraft.yaml`` file. Using the +``flask-framework`` profile, Rockcraft will automate the creation of +``rockcraft.yaml`` and tailor the file for a Flask application. +From the `flask-hello-world` directory, initialize the rock: .. literalinclude:: code/flask/task.yaml :language: bash @@ -123,8 +133,39 @@ based on your working directory. Choosing a different name or running on a platform different from ``amd64`` will influence the names of the files generated by Rockcraft. -Open the file in a text editor and check that the ``name`` is -``flask-hello-world``. +Check out the contents of ``rockcraft.yaml``: + +.. code:: bash + + cat rockcraft.yaml + +The top of the file should look similar to the following snippet: + +.. code:: yaml + + name: flask-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Flask application + version: '0.1' # just for humans. Semantic versioning is recommended + summary: A summary of your Flask application # 79 char long summary + description: | + This is flask-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + + +Verify that the ``name`` is ``flask-hello-world``. Ensure that ``platforms`` includes the architecture of your host. Check the architecture of your system: @@ -134,7 +175,8 @@ the architecture of your system: dpkg --print-architecture -If your host uses the ARM architecture, include ``arm64`` in ``platforms``. +If your host uses the ARM architecture, open ``rockcraft.yaml`` in a +text editor and include ``arm64`` under ``platforms``. Now let's pack the rock: @@ -144,20 +186,19 @@ Now let's pack the rock: :end-before: [docs:pack-end] :dedent: 2 -Depending on your system and network, this step can take a couple of -minutes to finish. +Depending on your system and network, this step can take several +minutes to finish. Once Rockcraft has finished packing the Flask rock, +the terminal will respond with something similar to +``Packed flask-hello-world_0.1_amd64.rock``. -Once Rockcraft has finished packing the Flask rock, you'll find a new file -in your working directory with the ``.rock`` extension: +.. note:: -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:ls-rock] - :end-before: [docs:ls-rock-end] - :dedent: 2 + If you are not on an ``amd64`` platform, the name of the ``.rock`` file + will be different for you. -The rock needs to be copied to the MicroK8s registry so that it can be -deployed in the Kubernetes cluster: +The rock needs to be copied to the MicroK8s registry, which stores OCI +archives so they can be downloaded and deployed in the Kubernetes cluster. +Copy the rock: .. literalinclude:: code/flask/task.yaml :language: bash @@ -171,9 +212,10 @@ deployed in the Kubernetes cluster: `_ Create the charm -================ +---------------- -Let's create a new directory for the charm and go inside it: +From the ``flask-hello-world`` directory, let's create a new directory +for the charm and change inside it: .. literalinclude:: code/flask/task.yaml :language: bash @@ -181,10 +223,13 @@ Let's create a new directory for the charm and go inside it: :end-before: [docs:create-charm-dir-end] :dedent: 2 -We'll need a ``charmcraft.yaml``, ``requirements.txt`` and source code for -the charm. The source code contains the logic required to operate the Flask -application. Charmcraft will automate the creation of these files by using -the ``flask-framework`` profile: +Using the ``flask-framework`` profile, Charmcraft will automate the +creation of the files needed for our charm, including a +``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. +The source code contains the logic required to operate the Flask +application. + +Initialize a charm named ``flask-hello-world``: .. literalinclude:: code/flask/task.yaml :language: bash @@ -201,27 +246,23 @@ Let's pack the charm: :end-before: [docs:charm-pack-end] :dedent: 2 -Depending on your system and network, this step can take a couple -of minutes to finish. - -Once Charmcraft has finished packing the charm, you'll find a new file in your -working directory with the ``.charm`` extension: +Depending on your system and network, this step can take several +minutes to finish. -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:ls-charm] - :end-before: [docs:ls-charm-end] - :dedent: 2 +Once Charmcraft has finished packing the charm, the terminal will +respond with something similar to +``Pack flask-hello-world_ubuntu-24.04-amd64.charm``. .. note:: - If you changed the name in charmcraft.yaml or are not on the amd64 platform, - the name of the ``.charm`` file will be different for you. + If you are not on the ``amd64`` platform, the name of the ``.charm`` + file will be different for you. Deploy the Flask application -============================ +---------------------------- -A Juju model is needed to deploy the application. Let's create a new model: +A Juju model is needed to handle Kubernetes resources while deploying +the Flask application. Let's create a new model: .. literalinclude:: code/flask/task.yaml :language: bash @@ -230,33 +271,47 @@ A Juju model is needed to deploy the application. Let's create a new model: :dedent: 2 If you are not on a host with the ``amd64`` architecture, you will need to include -a constraint to the Juju model to specify your architecture. Check the -architecture of your system using ``dpkg --print-architecture``. +to include a constraint to the Juju model to specify your architecture. +Check the architecture of your system using +``dpkg --print-architecture``. -For the ``arm64`` architecture, set the model constraints using +Set the Juju model constraints using -.. code-block:: - - juju set-model-constraints -m flask-hello-world arch=arm64 +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:add-model-constraints] + :end-before: [docs:add-model-constraints-end] + :dedent: 2 -Now the Flask application can be deployed using Juju: +Now let’s use the OCI image we previously uploaded to deploy the Flask +application. Deploy using Juju by specifying the OCI image name with the +``--resource`` option: .. literalinclude:: code/flask/task.yaml :language: bash - :start-after: [docs:deploy-juju-model] - :end-before: [docs:deploy-juju-model-end] + :start-after: [docs:deploy-flask-app] + :end-before: [docs:deploy-flask-app-end] :dedent: 2 It will take a few minutes to deploy the Flask application. You can monitor the -progress using ``juju status --watch 5s``. Once the status of the App has gone -to ``active``, you can stop watching using :kbd:`Ctrl` + :kbd:`C`. +progress using + +.. code:: bash + + juju status --relations --watch 2s + +The ``--relations`` flag will list the currently enabled integrations. +It can take a couple of minutes for the apps to finish the deployment. +Once the status of the App has gone to ``active``, you can stop watching +using :kbd:`Ctrl` + :kbd:`C`. .. seealso:: See more: `Command 'juju status' `_ -The Flask application should now be running. We can monitor the status of the deployment -using ``juju status`` which should be similar to the following output: +The Flask application should now be running. We can monitor the status of +the deployment using ``juju status`` which should be similar to the +following output: .. terminal:: @@ -269,9 +324,10 @@ using ``juju status`` which should be similar to the following output: Unit Workload Agent Address Ports Message flask-hello-world/0* active idle 10.1.87.213 -The deployment is finished when the status shows ``active``. Let's expose the -application using ingress. Deploy the ``nginx-ingress-integrator`` charm and integrate -it with the Flask app: +The deployment is finished when the status shows ``active``. + +Let's expose the application using ingress. Deploy the +``nginx-ingress-integrator`` charm and integrate it with the Flask app: .. literalinclude:: code/flask/task.yaml :language: bash @@ -279,8 +335,8 @@ it with the Flask app: :end-before: [docs:deploy-nginx-end] :dedent: 2 -The hostname of the app needs to be defined so that it is accessible via the ingress. -We will also set the default route to be the root endpoint: +The hostname of the app needs to be defined so that it is accessible via +the ingress. We will also set the default route to be the root endpoint: .. literalinclude:: code/flask/task.yaml :language: bash @@ -288,8 +344,9 @@ We will also set the default route to be the root endpoint: :end-before: [docs:config-nginx-end] :dedent: 2 -Monitor ``juju status`` until everything has a status of ``active``. Test the -deployment using +Monitor ``juju status`` until everything has a status of ``active``. + +Test the deployment using ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` to send a request via the ingress to the root endpoint. It should still be returning the ``Hello, world!`` greeting. @@ -301,18 +358,45 @@ the ``Hello, world!`` greeting. setting a DNS record. Configure the Flask application -=============================== +------------------------------- -Now let's customise the greeting using a configuration option. We will expect this +To demonstrate how to provide a configuration to the Flask application, +we will make the greeting configurable. We will expect this configuration option to be available in the Flask app configuration under the -keyword ``GREETING``. Go back out to the root directory of the project using +keyword ``GREETING``. Change back to the ``flask-hello-world`` directory using ``cd ..`` and copy the following code into ``app.py``: .. literalinclude:: code/flask/greeting_app.py :language: python -Open ``rockcraft.yaml`` and update the version to ``0.2``. Run ``rockcraft pack`` -again, then upload the new OCI image to the MicroK8s registry: +Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code-block:: yaml + :emphasize-lines: 5 + + name: flask-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Flask application + version: '0.2' # just for humans. Semantic versioning is recommended + summary: A summary of your Flask application # 79 char long summary + description: | + This is flask-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Let's run the pack and upload commands for the rock: .. literalinclude:: code/flask/task.yaml :language: bash @@ -320,30 +404,24 @@ again, then upload the new OCI image to the MicroK8s registry: :end-before: [docs:docker-update-end] :dedent: 2 -Change back into the charm directory using ``cd charm``. The ``flask-framework`` -Charmcraft extension supports adding configurations to ``charmcraft.yaml`` which -will be passed as environment variables to the Flask application. Add the -following to the end of the ``charmcraft.yaml`` file: +Change back into the charm directory using ``cd charm``. -.. code-block:: yaml +The ``flask-framework`` Charmcraft extension supports adding +configurations to ``charmcraft.yaml`` which will be passed as +environment variables to the Flask application. Add the following to +the end of the ``charmcraft.yaml`` file: - config: - options: - greeting: - description: | - The greeting to be returned by the Flask application. - default: "Hello, world!" - type: string +.. literalinclude:: code/flask/greeting_charmcraft.yaml + :language: yaml .. note:: - Configuration options are automatically capitalised and ``-`` are replaced + Configuration options are automatically capitalized and ``-`` are replaced by ``_``. A ``FLASK_`` prefix will also be added which will let Flask identify which environment variables to include when running ``app.config.from_prefixed_env()`` in ``app.py``. -Run ``charmcraft pack`` again. We can now refresh the deployment to -make use of the new code: +We can now pack and deploy the new version of the Flask app: .. literalinclude:: code/flask/task.yaml :language: bash @@ -351,7 +429,8 @@ make use of the new code: :end-before: [docs:refresh-deployment-end] :dedent: 2 -Wait for ``juju status`` to show that the App is ``active`` again. Verify that +After we wait for a bit monitoring ``juju status`` the application +should go back to ``active`` again. Verify that the new configuration has been added using ``juju config flask-hello-world | grep -A 6 greeting:`` which should show the configuration option. @@ -363,7 +442,8 @@ the configuration option. Using ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` shows that the response is still ``Hello, world!`` as expected. -The greeting can be changed using Juju: + +Now let’s change the greeting: .. literalinclude:: code/flask/task.yaml :language: bash @@ -371,15 +451,12 @@ The greeting can be changed using Juju: :end-before: [docs:change-config-end] :dedent: 2 +After we wait for a moment for the app to be restarted, using ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` -now returns the updated ``Hi!`` greeting. - -.. note:: - - It might take a short time for the configuration to take effect. +should now return the updated ``Hi!`` greeting. Integrate with a database -========================= +------------------------- Now let's keep track of how many visitors your application has received. This will require integration with a database to keep the visitor count. @@ -394,11 +471,12 @@ This will require a few changes: Let's start with the database migration to create the required tables. The charm created by the ``flask-framework`` extension will execute the ``migrate.py`` script if it exists. This script should ensure that the -database is initialised and ready to be used by the application. We will +database is initialized and ready to be used by the application. We will create a ``migrate.py`` file containing this logic. -Go back out to the tutorial root directory using ``cd ..``, open the ``migrate.py`` -file using a text editor and paste the following code into it: +Go back out to the ``flask-hello-world`` directory using ``cd ..``, +open the ``migrate.py`` file using a text editor and paste the +following code into it: .. literalinclude:: code/flask/visitors_migrate.py :language: python @@ -407,22 +485,46 @@ file using a text editor and paste the following code into it: The charm will pass the Database connection string in the ``POSTGRESQL_DB_CONNECT_STRING`` environment variable once - postgres has been integrated with the charm. + PostgreSQL has been integrated with the charm. -Open the ``rockcraft.yaml`` file in a text editor and update the version to ``0.3``. +Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: -To be able to connect to postgresql from the Flask app the ``psycopg2-binary`` -dependency needs to be added in ``requirements.txt``. The app code also needs -to be updated to keep track of the number of visitors and to include a new -endpoint to retrieve the number of visitors to the app. Open ``app.py`` in -a text editor and replace its contents with the following code: +.. code-block:: yaml + :emphasize-lines: 5 + + name: flask-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Flask application + version: '0.3' # just for humans. Semantic versioning is recommended + summary: A summary of your Flask application # 79 char long summary + description: | + This is flask-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +The app code also needs to be updated to keep track of the number of visitors +and to include a new endpoint to retrieve the number of visitors to the +app. Open ``app.py`` in a text editor and replace its contents with the +following code: .. collapse:: visitors_app.py .. literalinclude:: code/flask/visitors_app.py :language: python -Run ``rockcraft pack`` and upload the newly created rock to the MicroK8s registry: +Let's run the pack and upload commands for the rock: .. literalinclude:: code/flask/task.yaml :language: bash @@ -430,18 +532,16 @@ Run ``rockcraft pack`` and upload the newly created rock to the MicroK8s registr :end-before: [docs:docker-2nd-update-end] :dedent: 2 -Go back into the charm directory using ``cd charm``. The Flask app now requires -a database which needs to be declared in the ``charmcraft.yaml`` file. Open -``charmcraft.yaml`` in a text editor and add the following section to the end: +Change back into the charm directory using ``cd charm``. -.. code-block:: yaml +The Flask app now requires a database which needs to be declared in the +``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in a text editor and +add the following section to the end: - requires: - postgresql: - interface: postgresql_client - optional: false +.. literalinclude:: code/flask/visitors_charmcraft.yaml + :language: yaml -Pack the charm using ``charmcraft pack`` and refresh the deployment using Juju: +We can now pack and deploy the new version of the Flask app: .. literalinclude:: code/flask/task.yaml :language: bash @@ -449,7 +549,7 @@ Pack the charm using ``charmcraft pack`` and refresh the deployment using Juju: :end-before: [docs:refresh-2nd-deployment-end] :dedent: 2 -Deploy ``postgresql-k8s`` using Juju and integrate it with ``flask-hello-world``: +Now let’s deploy PostgreSQL and integrate it with the Flask application: .. literalinclude:: code/flask/task.yaml :language: bash @@ -471,11 +571,23 @@ If we perform another request to ``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` will return ``2``. -Clean up the environment -======================== +Tear things down +---------------- + +We’ve reached the end of this tutorial. We went through the entire +development process, including: + +- Creating a Flask application +- Deploying the application locally +- Building an OCI image using Rockcraft +- Packaging the application using Charmcraft +- Deplyoing the application using Juju +- Exposing the application using an ingress +- Adding an initial app and configuring the application +- Integrating the application with a database If you'd like to reset your working environment, you can run the following -in the root directory for the tutorial: +in the rock directory ``flask-hello-world`` for the tutorial: .. literalinclude:: code/flask/task.yaml :language: bash @@ -483,8 +595,7 @@ in the root directory for the tutorial: :end-before: [docs:clean-environment-end] :dedent: 2 -You can also clean up the Multipass instance. -Start by exiting it: +You can also clean up your Multipass instance. Start by exiting it: .. code-block:: bash @@ -497,11 +608,11 @@ And then you can proceed with its deletion: multipass delete charm-dev multipass purge -We've reached the end of this tutorial. We have created a Flask application, -deployed it locally, exposed it via ingress and integrated it with a database! - Next steps -========== +---------- + +By the end of this tutorial you will have built a charm and evolved it +in a number of typical ways. But there is a lot more to explore: .. list-table:: :widths: 30 30 @@ -510,12 +621,12 @@ Next steps * - If you are wondering... - Visit... * - "How do I...?" - - `SDK How-to docs `_ + - :ref:`how-to-guides` * - "How do I debug?" - `Charm debugging tools `_ * - "How do I get in touch?" - `Matrix channel `_ * - "What is...?" - - `SDK Reference docs `_ + - :ref:`reference` * - "Why...?", "So what?" - - `SDK Explanation docs `_ + - :ref:`explanation` diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 8a4066583..736167359 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -305,7 +305,8 @@ Copy the rock: .. seealso:: - See more: `skopeo `_ + See more: `Ubuntu manpage | skopeo + `_ Create the charm ---------------- @@ -433,7 +434,11 @@ waits to become integrated with the PostgreSQL database. Once the status of the App has gone to ``active``, you can stop watching using :kbd:`Ctrl` + :kbd:`C`. -The Django application should now be running. You can see the status of +.. seealso:: + + See more: `Command 'juju status' `_ + +The Django application should now be running. We can see the status of the deployment using ``juju status`` which should be similar to the following output: @@ -755,20 +760,19 @@ Next steps By the end of this tutorial you will have built a charm and evolved it in a number of typical ways. But there is a lot more to explore: -+--------------------------+------------------------------------------+ -| If you are wondering… | visit… | -+==========================+==========================================+ -| “How do I…?” | :ref:`how-to-guides` | -+--------------------------+------------------------------------------+ -| “How do I debug?” | `Charm debugging | -| | tools `__ | -+--------------------------+------------------------------------------+ -| “How do I get in touch?” | `Matrix | -| | channel `__ | -+--------------------------+------------------------------------------+ -| “What is…?” | :ref:`reference` | -+--------------------------+------------------------------------------+ -| “Why…?”, “So what?” | :ref:`explanation` | -+--------------------------+------------------------------------------+ +.. list-table:: + :widths: 30 30 + :header-rows: 1 + + * - If you are wondering... + - Visit... + * - "How do I...?" + - :ref:`how-to-guides` + * - "How do I debug?" + - `Charm debugging tools `_ + * - "How do I get in touch?" + - `Matrix channel `_ + * - "What is...?" + - :ref:`reference` + * - "Why...?", "So what?" + - :ref:`explanation` \ No newline at end of file From 1438529bb30a5fc12c5d51dc0d9a820aa0edf032 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 14:16:31 -0500 Subject: [PATCH 19/51] fix: make lint happy --- docs/tutorial/flask.rst | 10 +++++----- ...te-your-first-kubernetes-charm-for-a-django-app.rst | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/flask.rst b/docs/tutorial/flask.rst index 3ee6242a1..95ad651a4 100644 --- a/docs/tutorial/flask.rst +++ b/docs/tutorial/flask.rst @@ -109,7 +109,7 @@ endpoint. You will need a new terminal for this; use :end-before: [docs:curl-flask-end] :dedent: 2 -The Flask application should respond with ``Hello, world!``. +The Flask application should respond with ``Hello, world!``. The Flask application looks good, so we can stop for now using :kbd:`Ctrl` + :kbd:`C`. @@ -120,7 +120,7 @@ Pack the Flask application into a rock First, we'll need a ``rockcraft.yaml`` file. Using the ``flask-framework`` profile, Rockcraft will automate the creation of ``rockcraft.yaml`` and tailor the file for a Flask application. -From the `flask-hello-world` directory, initialize the rock: +From the ``flask-hello-world`` directory, initialize the rock: .. literalinclude:: code/flask/task.yaml :language: bash @@ -227,7 +227,7 @@ Using the ``flask-framework`` profile, Charmcraft will automate the creation of the files needed for our charm, including a ``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. The source code contains the logic required to operate the Flask -application. +application. Initialize a charm named ``flask-hello-world``: @@ -298,7 +298,7 @@ progress using .. code:: bash - juju status --relations --watch 2s + juju status --relations --watch 2s The ``--relations`` flag will list the currently enabled integrations. It can take a couple of minutes for the apps to finish the deployment. @@ -370,7 +370,7 @@ keyword ``GREETING``. Change back to the ``flask-hello-world`` directory using :language: python Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the -top of the ``rockcraft.yaml`` file looks similar to the following: +top of the ``rockcraft.yaml`` file looks similar to the following: .. code-block:: yaml :emphasize-lines: 5 diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 736167359..69f7a2604 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -775,4 +775,4 @@ in a number of typical ways. But there is a lot more to explore: * - "What is...?" - :ref:`reference` * - "Why...?", "So what?" - - :ref:`explanation` \ No newline at end of file + - :ref:`explanation` From a01b53beb75cf155a99d185f371f5f1c3e1fd5d6 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 14:58:50 -0500 Subject: [PATCH 20/51] docs: update setup instructions --- docs/reuse/tutorial/setup_edge.rst | 4 ++-- docs/reuse/tutorial/setup_stable.rst | 25 ++++++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst index 66f2608cf..632a329cb 100644 --- a/docs/reuse/tutorial/setup_edge.rst +++ b/docs/reuse/tutorial/setup_edge.rst @@ -21,8 +21,8 @@ In order to create the rock, you'll need to install Rockcraft with the sudo snap install rockcraft --classic -``LXD`` will be required for building the rock. Make sure it is installed -and initialised: +``LXD`` will be required for building the rock. +Make sure it is installed and initialised: .. code-block:: bash diff --git a/docs/reuse/tutorial/setup_stable.rst b/docs/reuse/tutorial/setup_stable.rst index 7737854e9..0fb76018d 100644 --- a/docs/reuse/tutorial/setup_stable.rst +++ b/docs/reuse/tutorial/setup_stable.rst @@ -13,7 +13,8 @@ Once the VM is up, open a shell into it: multipass shell charm-dev -In order to create the rock, you'll need to install Rockcraft: +In order to create the rock, you'll need to install Rockcraft with the +``--classic`` confinement to allow access to the whole file system: .. code-block:: bash @@ -24,9 +25,11 @@ Make sure it is installed and initialised: .. code-block:: bash - sudo snap install lxd + lxd --version lxd init --auto +If ``LXD`` is not installed, install it with ``sudo snap install lxd``. + In order to create the charm, you'll need to install Charmcraft: .. code-block:: bash @@ -41,7 +44,8 @@ In order to create the charm, you'll need to install Charmcraft: ``sudo snap refresh charmcraft --channel latest/edge`` to get the latest edge version of Charmcraft. -MicroK8s is required to deploy the Flask application on Kubernetes. Install MicroK8s: +MicroK8s is required to deploy the Flask application on Kubernetes. +Let's install MicroK8s using the ``1.31-strict/stable`` track: .. code-block:: bash @@ -49,11 +53,17 @@ MicroK8s is required to deploy the Flask application on Kubernetes. Install Micr sudo adduser $USER snap_microk8s newgrp snap_microk8s -Wait for MicroK8s to be ready using ``sudo microk8s status --wait-ready``. +Wait for MicroK8s to be ready: + +.. code-block:: bash + + sudo microk8s status --wait-ready + Several MicroK8s add-ons are required for deployment: .. code-block:: bash + # Required for Juju to provide storage volumes sudo microk8s enable hostpath-storage # Required to host the OCI image of the Flask application sudo microk8s enable registry @@ -61,10 +71,15 @@ Several MicroK8s add-ons are required for deployment: sudo microk8s enable ingress Juju is required to deploy the Flask application. -Install Juju and bootstrap a development controller: +Install Juju using the ``3.5/stable`` track, and bootstrap a +development controller: .. code-block:: bash sudo snap install juju --channel 3.5/stable mkdir -p ~/.local/share juju bootstrap microk8s dev-controller + +.. note:: + + It could take a few minutes to download the images. \ No newline at end of file From f78d2f9313e2528242edf3565eba29f0e99e0f4c Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 23 Jan 2025 15:02:47 -0500 Subject: [PATCH 21/51] fix: make lint happy --- docs/reuse/tutorial/setup_edge.rst | 2 +- docs/reuse/tutorial/setup_stable.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst index 632a329cb..a24a49faa 100644 --- a/docs/reuse/tutorial/setup_edge.rst +++ b/docs/reuse/tutorial/setup_edge.rst @@ -21,7 +21,7 @@ In order to create the rock, you'll need to install Rockcraft with the sudo snap install rockcraft --classic -``LXD`` will be required for building the rock. +``LXD`` will be required for building the rock. Make sure it is installed and initialised: .. code-block:: bash diff --git a/docs/reuse/tutorial/setup_stable.rst b/docs/reuse/tutorial/setup_stable.rst index 0fb76018d..3631f53fa 100644 --- a/docs/reuse/tutorial/setup_stable.rst +++ b/docs/reuse/tutorial/setup_stable.rst @@ -82,4 +82,4 @@ development controller: .. note:: - It could take a few minutes to download the images. \ No newline at end of file + It could take a few minutes to download the images. From da355e72b8b5a496089d4bbdac4dafa18f54241d Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 24 Jan 2025 08:25:31 -0500 Subject: [PATCH 22/51] test: turn on tutorial spread tests --- spread.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread.yaml b/spread.yaml index 834d7ffb2..9eb07fabc 100644 --- a/spread.yaml +++ b/spread.yaml @@ -139,7 +139,7 @@ suites: summary: tests tutorial from the docs systems: - ubuntu-22.04-64 - manual: true + #manual: true tests/spread/commands/: summary: simple charmcraft commands tests/spread/charms/: From 0d7ce155a2ce7d5efeb85a775565a9ba8e262d96 Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 24 Jan 2025 12:06:24 -0500 Subject: [PATCH 23/51] test: force juju snap refresh --- docs/tutorial/code/django/task.yaml | 1 + docs/tutorial/code/flask/task.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index 184f31959..f6dc488a9 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -29,6 +29,7 @@ execute: | lxd init --auto snap install microk8s --channel=1.31-strict/stable snap install juju --channel=3.5/stable + snap refresh juju --channel=3.5/stable --amend # Juju config setup lxc network set lxdbr0 ipv6.address none diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 3c45dbdf6..efabc7f40 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -29,6 +29,7 @@ execute: | lxd init --auto snap install microk8s --channel=1.31-strict/stable snap install juju --channel=3.5/stable + snap refresh juju --channel=3.5/stable --amend # Juju config setup lxc network set lxdbr0 ipv6.address none From 9f34fe12905bdd0da45ca220fa86a2b34a6291df Mon Sep 17 00:00:00 2001 From: erinecon Date: Tue, 28 Jan 2025 14:35:19 -0500 Subject: [PATCH 24/51] fix: add substitutions to setup --- docs/reuse/tutorial/setup_edge.rst | 14 +++++++++----- docs/reuse/tutorial/setup_stable.rst | 14 +++++++++----- docs/tutorial/code/django/task.yaml | 2 +- docs/tutorial/code/flask/task.yaml | 2 +- docs/tutorial/flask.rst | 3 ++- ...our-first-kubernetes-charm-for-a-django-app.rst | 3 ++- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst index a24a49faa..6e0ac9432 100644 --- a/docs/reuse/tutorial/setup_edge.rst +++ b/docs/reuse/tutorial/setup_edge.rst @@ -1,4 +1,8 @@ -First, `install Multipass `_. +First, install Multipass. + +.. seealso:: + + See more: `Multipass | How to install Multipass `_ Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` from the 24.04 blueprint: @@ -43,7 +47,7 @@ In order to create the charm, you'll need to install Charmcraft: This tutorial requires version ``3.2.0`` or later of Charmcraft. Check the version of Charmcraft using ``charmcraft --version``. -MicroK8s is required to deploy the Django application on Kubernetes. +MicroK8s is required to deploy the |12FactorApp| application on Kubernetes. Let's install MicroK8s using the ``1.31-strict/stable`` track: .. code-block:: bash @@ -64,12 +68,12 @@ Several MicroK8s add-ons are required for deployment: # Required for Juju to provide storage volumes sudo microk8s enable hostpath-storage - # Required to host the OCI image of the Django application + # Required to host the OCI image of the 12-Factor application sudo microk8s enable registry - # Required to expose the Django application + # Required to expose the 12-Factor application sudo microk8s enable ingress -Juju is required to deploy the Django application. +Juju is required to deploy the |12FactorApp| application. Install Juju using the ``3.5/stable`` track, and bootstrap a development controller: diff --git a/docs/reuse/tutorial/setup_stable.rst b/docs/reuse/tutorial/setup_stable.rst index 3631f53fa..582bbc927 100644 --- a/docs/reuse/tutorial/setup_stable.rst +++ b/docs/reuse/tutorial/setup_stable.rst @@ -1,4 +1,8 @@ -First, `install Multipass `_. +First, install Multipass. + +.. seealso:: + + See more: `Multipass | How to install Multipass `_ Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` from the 24.04 blueprint: @@ -44,7 +48,7 @@ In order to create the charm, you'll need to install Charmcraft: ``sudo snap refresh charmcraft --channel latest/edge`` to get the latest edge version of Charmcraft. -MicroK8s is required to deploy the Flask application on Kubernetes. +MicroK8s is required to deploy the |12FactorApp| application on Kubernetes. Let's install MicroK8s using the ``1.31-strict/stable`` track: .. code-block:: bash @@ -65,12 +69,12 @@ Several MicroK8s add-ons are required for deployment: # Required for Juju to provide storage volumes sudo microk8s enable hostpath-storage - # Required to host the OCI image of the Flask application + # Required to host the OCI image of the 12-Factor application sudo microk8s enable registry - # Required to expose the Flask application + # Required to expose the 12-Factor application sudo microk8s enable ingress -Juju is required to deploy the Flask application. +Juju is required to deploy the |12FactorApp| application. Install Juju using the ``3.5/stable`` track, and bootstrap a development controller: diff --git a/docs/tutorial/code/django/task.yaml b/docs/tutorial/code/django/task.yaml index f6dc488a9..d066c7edc 100644 --- a/docs/tutorial/code/django/task.yaml +++ b/docs/tutorial/code/django/task.yaml @@ -7,7 +7,7 @@ ########################################### summary: Getting started with Django tutorial -kill-timeout: 90m +kill-timeout: 180m environment: diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index efabc7f40..2e69e9bb2 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -196,7 +196,7 @@ execute: | # [docs:deploy-postgres-end] # give Juju some time to deploy and refresh the apps - juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m + juju wait-for application postgresql-k8s --query='status=="active"' --timeout 30m juju wait-for application flask-hello-world --query='status=="active"' --timeout 30m | juju status --relations juju status --relations diff --git a/docs/tutorial/flask.rst b/docs/tutorial/flask.rst index 95ad651a4..b5cd8d71e 100644 --- a/docs/tutorial/flask.rst +++ b/docs/tutorial/flask.rst @@ -43,6 +43,7 @@ Set things up ------------- .. include:: /reuse/tutorial/setup_stable.rst +.. |12FactorApp| replace:: Flask Let's create a new directory for this tutorial and change into it: @@ -628,5 +629,5 @@ in a number of typical ways. But there is a lot more to explore: - `Matrix channel `_ * - "What is...?" - :ref:`reference` - * - "Why...?", "So what?" +.. * - "Why...?", "So what?" - :ref:`explanation` diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index 69f7a2604..bef5db662 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -44,6 +44,7 @@ Set things up ------------- .. include:: /reuse/tutorial/setup_edge.rst +.. |12FactorApp| replace:: Django Let’s create a new directory for this tutorial and change into it: @@ -774,5 +775,5 @@ in a number of typical ways. But there is a lot more to explore: - `Matrix channel `_ * - "What is...?" - :ref:`reference` - * - "Why...?", "So what?" +.. * - "Why...?", "So what?" - :ref:`explanation` From a1badee7998ede7716786d587f0c42dca188ed82 Mon Sep 17 00:00:00 2001 From: erinecon Date: Tue, 28 Jan 2025 14:46:32 -0500 Subject: [PATCH 25/51] fix: make lint happy --- docs/reuse/tutorial/setup_edge.rst | 3 ++- docs/reuse/tutorial/setup_stable.rst | 3 ++- ...-your-first-kubernetes-charm-for-a-django-app.rst | 12 ++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst index 6e0ac9432..66a9bc928 100644 --- a/docs/reuse/tutorial/setup_edge.rst +++ b/docs/reuse/tutorial/setup_edge.rst @@ -2,7 +2,8 @@ First, install Multipass. .. seealso:: - See more: `Multipass | How to install Multipass `_ + See more: `Multipass | + How to install Multipass `_ Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` from the 24.04 blueprint: diff --git a/docs/reuse/tutorial/setup_stable.rst b/docs/reuse/tutorial/setup_stable.rst index 582bbc927..8aaacfd37 100644 --- a/docs/reuse/tutorial/setup_stable.rst +++ b/docs/reuse/tutorial/setup_stable.rst @@ -2,7 +2,8 @@ First, install Multipass. .. seealso:: - See more: `Multipass | How to install Multipass `_ + See more: `Multipass | + How to install Multipass `_ Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` from the 24.04 blueprint: diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index bef5db662..a48143c15 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -768,12 +768,16 @@ in a number of typical ways. But there is a lot more to explore: * - If you are wondering... - Visit... * - "How do I...?" - - :ref:`how-to-guides` + - :ref:`How-to guides `, + :external+ops:ref:`Ops | How-to guides ` * - "How do I debug?" - `Charm debugging tools `_ * - "How do I get in touch?" - `Matrix channel `_ * - "What is...?" - - :ref:`reference` -.. * - "Why...?", "So what?" - - :ref:`explanation` + - :ref:`reference`, + :external+ops:ref:`Ops | Reference `, + :external+juju:ref:`Juju | Reference ` + * - "Why...?", "So what?" + - :external+ops:ref:`Ops | Explanation `, + :external+juju:ref:`Juju | Explanation ` From 48905f82b96c90f245ef90872ae728805994431a Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 10:22:20 -0500 Subject: [PATCH 26/51] docs: update flask tutorial title --- docs/tutorial/flask.rst | 637 ---------------------------------------- 1 file changed, 637 deletions(-) delete mode 100644 docs/tutorial/flask.rst diff --git a/docs/tutorial/flask.rst b/docs/tutorial/flask.rst deleted file mode 100644 index d0a053764..000000000 --- a/docs/tutorial/flask.rst +++ /dev/null @@ -1,637 +0,0 @@ -================================================= -Write your first Kubernetes charm for a Flask app -================================================= - -Imagine you have a Flask application backed up by a database -such as PostgreSQL and need to deploy it. In a traditional setup, -this can be quite a challenge, but with Charmcraft you'll find -yourself packaging and deploying your Flask application in no time. -Let's get started! - -In this tutorial we will build a Kubernetes charm for a Flask -application using Charmcraft, so we can have a Flask application -up and running with Juju. - -This tutorial should take 90 minutes for you to complete. - -.. note:: - If you're new to the charming world: Flask applications are - specifically supported with a coordinated pair of profiles - for an OCI container image (**rock**) and corresponding - packaged software (**charm**) that allow for the application - to be deployed, integrated and operated on a Kubernetes - cluster with the Juju orchestration engine. - -What you'll need ----------------- - -- A workstation, e.g., a laptop, with amd64 or arm64 architecture which - has sufficient resources to launch a virtual machine with 4 CPUs, - 4 GB RAM, and a 50 GB disk -- Familiarity with Linux - -What you'll do --------------- - -Create a Flask application. Use that to create a rock with -``rockcraft``. Use that to create a charm with ``charmcraft``. Use that -to test-deploy, configure, etc., your Flask application on a local -Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple -times, mimicking a real development process. - -Set things up -------------- - -.. include:: /reuse/tutorial/setup_stable.rst -.. |12FactorApp| replace:: Flask - -Let's create a new directory for this tutorial and change into it: - -.. code-block:: bash - - mkdir flask-hello-world - cd flask-hello-world - -Finally, install ``python3-venv`` and create a virtual environment: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:create-venv] - :end-before: [docs:create-venv-end] - :dedent: 2 - -Create the Flask application ----------------------------- - -Let's start by creating the "Hello, world" Flask application that -will be used for this tutorial. - -Create a ``requirements.txt`` file, copy the following text into it -and then save it: - -.. literalinclude:: code/flask/requirements.txt - -.. note:: - - The ``psycopg2-binary`` package is needed so the Flask application can - connect to PostgreSQL. - -Install the packages: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:install-requirements] - :end-before: [docs:install-requirements-end] - :dedent: 2 - -In the same directory, copy and save the following into a text file -called ``app.py``: - -.. literalinclude:: code/flask/app.py - :language: python - -Run the Flask application locally ---------------------------------- - -Now that we have a virtual environment with all the dependencies, let's -run the Flask application to verify that it works: - -.. code-block:: bash - - flask run -p 8000 - -Test the Flask application by using ``curl`` to send a request to the root -endpoint. You will need a new terminal for this; use -``multipass shell charm-dev`` to get another terminal: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:curl-flask] - :end-before: [docs:curl-flask-end] - :dedent: 2 - -The Flask application should respond with ``Hello, world!``. - -The Flask application looks good, so we can stop for now using -:kbd:`Ctrl` + :kbd:`C`. - -Pack the Flask application into a rock --------------------------------------- - -First, we'll need a ``rockcraft.yaml`` file. Using the -``flask-framework`` profile, Rockcraft will automate the creation of -``rockcraft.yaml`` and tailor the file for a Flask application. -From the ``flask-hello-world`` directory, initialize the rock: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:create-rockcraft-yaml] - :end-before: [docs:create-rockcraft-yaml-end] - :dedent: 2 - -The ``rockcraft.yaml`` file will automatically be created and set the name -based on your working directory. Choosing a different name or running on -a platform different from ``amd64`` will influence the names of the files -generated by Rockcraft. - -Check out the contents of ``rockcraft.yaml``: - -.. code:: bash - - cat rockcraft.yaml - -The top of the file should look similar to the following snippet: - -.. code:: yaml - - name: flask-hello-world - # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ - # for more information about bases and using 'bare' bases for chiselled rocks - base: ubuntu@22.04 # the base environment for this Flask application - version: '0.1' # just for humans. Semantic versioning is recommended - summary: A summary of your Flask application # 79 char long summary - description: | - This is flask-hello-world's description. You have a paragraph or two to tell the - most important story about it. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the - container registries out there. - # the platforms this rock should be built on and run on. - # you can check your architecture with `dpkg --print-architecture` - platforms: - amd64: - # arm64: - # ppc64el: - # s390x: - - ... - - -Verify that the ``name`` is ``flask-hello-world``. - -Ensure that ``platforms`` includes the architecture of your host. Check -the architecture of your system: - -.. code-block:: bash - - dpkg --print-architecture - - -If your host uses the ARM architecture, open ``rockcraft.yaml`` in a -text editor and include ``arm64`` under ``platforms``. - -Now let's pack the rock: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:pack] - :end-before: [docs:pack-end] - :dedent: 2 - -Depending on your system and network, this step can take several -minutes to finish. Once Rockcraft has finished packing the Flask rock, -the terminal will respond with something similar to -``Packed flask-hello-world_0.1_amd64.rock``. - -.. note:: - - If you are not on an ``amd64`` platform, the name of the ``.rock`` file - will be different for you. - -The rock needs to be copied to the MicroK8s registry, which stores OCI -archives so they can be downloaded and deployed in the Kubernetes cluster. -Copy the rock: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:skopeo-copy] - :end-before: [docs:skopeo-copy-end] - :dedent: 2 - -.. seealso:: - - See more: `Ubuntu manpage | skopeo - `_ - -Create the charm ----------------- - -From the ``flask-hello-world`` directory, let's create a new directory -for the charm and change inside it: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:create-charm-dir] - :end-before: [docs:create-charm-dir-end] - :dedent: 2 - -Using the ``flask-framework`` profile, Charmcraft will automate the -creation of the files needed for our charm, including a -``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. -The source code contains the logic required to operate the Flask -application. - -Initialize a charm named ``flask-hello-world``: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:charm-init] - :end-before: [docs:charm-init-end] - :dedent: 2 - -The files will automatically be created in your working directory. -Let's pack the charm: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:charm-pack] - :end-before: [docs:charm-pack-end] - :dedent: 2 - -Depending on your system and network, this step can take several -minutes to finish. - -Once Charmcraft has finished packing the charm, the terminal will -respond with something similar to -``Pack flask-hello-world_ubuntu-24.04-amd64.charm``. - -.. note:: - - If you are not on the ``amd64`` platform, the name of the ``.charm`` - file will be different for you. - -Deploy the Flask application ----------------------------- - -A Juju model is needed to handle Kubernetes resources while deploying -the Flask application. Let's create a new model: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:add-juju-model] - :end-before: [docs:add-juju-model-end] - :dedent: 2 - -If you are not on a host with the ``amd64`` architecture, you will need to include -to include a constraint to the Juju model to specify your architecture. -Check the architecture of your system using -``dpkg --print-architecture``. - -Set the Juju model constraints using - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:add-model-constraints] - :end-before: [docs:add-model-constraints-end] - :dedent: 2 - -Now let’s use the OCI image we previously uploaded to deploy the Flask -application. Deploy using Juju by specifying the OCI image name with the -``--resource`` option: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:deploy-flask-app] - :end-before: [docs:deploy-flask-app-end] - :dedent: 2 - -It will take a few minutes to deploy the Flask application. You can monitor the -progress using - -.. code:: bash - - juju status --relations --watch 2s - -The ``--relations`` flag will list the currently enabled integrations. -It can take a couple of minutes for the apps to finish the deployment. -Once the status of the App has gone to ``active``, you can stop watching -using :kbd:`Ctrl` + :kbd:`C`. - -.. seealso:: - - See more: :external+juju:ref:`Juju | juju status ` - -The Flask application should now be running. We can monitor the status of -the deployment using ``juju status`` which should be similar to the -following output: - -.. terminal:: - - Model Controller Cloud/Region Version SLA Timestamp - flask-hello-world dev-controller microk8s/localhost 3.1.8 unsupported 17:04:11+10:00 - - App Version Status Scale Charm Channel Rev Address Exposed Message - flask-hello-world active 1 flask-hello-world 0 10.152.183.166 no - - Unit Workload Agent Address Ports Message - flask-hello-world/0* active idle 10.1.87.213 - -The deployment is finished when the status shows ``active``. - -Let's expose the application using ingress. Deploy the -``nginx-ingress-integrator`` charm and integrate it with the Flask app: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:deploy-nginx] - :end-before: [docs:deploy-nginx-end] - :dedent: 2 - -The hostname of the app needs to be defined so that it is accessible via -the ingress. We will also set the default route to be the root endpoint: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:config-nginx] - :end-before: [docs:config-nginx-end] - :dedent: 2 - -Monitor ``juju status`` until everything has a status of ``active``. - -Test the deployment using -``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` to send -a request via the ingress to the root endpoint. It should still be returning -the ``Hello, world!`` greeting. - -.. note:: - - The ``--resolve flask-hello-world:80:127.0.0.1`` option to the ``curl`` - command is a way of resolving the hostname of the request without - setting a DNS record. - -Configure the Flask application -------------------------------- - -To demonstrate how to provide a configuration to the Flask application, -we will make the greeting configurable. We will expect this -configuration option to be available in the Flask app configuration under the -keyword ``GREETING``. Change back to the ``flask-hello-world`` directory using -``cd ..`` and copy the following code into ``app.py``: - -.. literalinclude:: code/flask/greeting_app.py - :language: python - -Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the -top of the ``rockcraft.yaml`` file looks similar to the following: - -.. code-block:: yaml - :emphasize-lines: 5 - - name: flask-hello-world - # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ - # for more information about bases and using 'bare' bases for chiselled rocks - base: ubuntu@22.04 # the base environment for this Flask application - version: '0.2' # just for humans. Semantic versioning is recommended - summary: A summary of your Flask application # 79 char long summary - description: | - This is flask-hello-world's description. You have a paragraph or two to tell the - most important story about it. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the - container registries out there. - # the platforms this rock should be built on and run on. - # you can check your architecture with `dpkg --print-architecture` - platforms: - amd64: - # arm64: - # ppc64el: - # s390x: - - ... - -Let's run the pack and upload commands for the rock: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:docker-update] - :end-before: [docs:docker-update-end] - :dedent: 2 - -Change back into the charm directory using ``cd charm``. - -The ``flask-framework`` Charmcraft extension supports adding -configurations to ``charmcraft.yaml`` which will be passed as -environment variables to the Flask application. Add the following to -the end of the ``charmcraft.yaml`` file: - -.. literalinclude:: code/flask/greeting_charmcraft.yaml - :language: yaml - -.. note:: - - Configuration options are automatically capitalized and ``-`` are replaced - by ``_``. A ``FLASK_`` prefix will also be added which will let Flask - identify which environment variables to include when running - ``app.config.from_prefixed_env()`` in ``app.py``. - -We can now pack and deploy the new version of the Flask app: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:refresh-deployment] - :end-before: [docs:refresh-deployment-end] - :dedent: 2 - -After we wait for a bit monitoring ``juju status`` the application -should go back to ``active`` again. Verify that -the new configuration has been added using -``juju config flask-hello-world | grep -A 6 greeting:`` which should show -the configuration option. - -.. note:: - - The ``grep`` command extracts a portion of the configuration to make - it easier to check whether the configuration option has been added. - -Using ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` -shows that the response is still ``Hello, world!`` as expected. - -Now let’s change the greeting: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:change-config] - :end-before: [docs:change-config-end] - :dedent: 2 - -After we wait for a moment for the app to be restarted, using -``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` -should now return the updated ``Hi!`` greeting. - -Integrate with a database -------------------------- - -Now let's keep track of how many visitors your application has received. -This will require integration with a database to keep the visitor count. -This will require a few changes: - -* We will need to create a database migration that creates the ``visitors`` table -* We will need to keep track how many times the root endpoint has been called - in the database -* We will need to add a new endpoint to retrieve the number of visitors from the - database - -Let's start with the database migration to create the required tables. -The charm created by the ``flask-framework`` extension will execute the -``migrate.py`` script if it exists. This script should ensure that the -database is initialized and ready to be used by the application. We will -create a ``migrate.py`` file containing this logic. - -Go back out to the ``flask-hello-world`` directory using ``cd ..``, -open the ``migrate.py`` file using a text editor and paste the -following code into it: - -.. literalinclude:: code/flask/visitors_migrate.py - :language: python - -.. note:: - - The charm will pass the Database connection string in the - ``POSTGRESQL_DB_CONNECT_STRING`` environment variable once - PostgreSQL has been integrated with the charm. - -Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the -top of the ``rockcraft.yaml`` file looks similar to the following: - -.. code-block:: yaml - :emphasize-lines: 5 - - name: flask-hello-world - # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ - # for more information about bases and using 'bare' bases for chiselled rocks - base: ubuntu@22.04 # the base environment for this Flask application - version: '0.3' # just for humans. Semantic versioning is recommended - summary: A summary of your Flask application # 79 char long summary - description: | - This is flask-hello-world's description. You have a paragraph or two to tell the - most important story about it. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the - container registries out there. - # the platforms this rock should be built on and run on. - # you can check your architecture with `dpkg --print-architecture` - platforms: - amd64: - # arm64: - # ppc64el: - # s390x: - - ... - -The app code also needs to be updated to keep track of the number of visitors -and to include a new endpoint to retrieve the number of visitors to the -app. Open ``app.py`` in a text editor and replace its contents with the -following code: - -.. collapse:: visitors_app.py - - .. literalinclude:: code/flask/visitors_app.py - :language: python - -Let's run the pack and upload commands for the rock: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:docker-2nd-update] - :end-before: [docs:docker-2nd-update-end] - :dedent: 2 - -Change back into the charm directory using ``cd charm``. - -The Flask app now requires a database which needs to be declared in the -``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in a text editor and -add the following section to the end: - -.. literalinclude:: code/flask/visitors_charmcraft.yaml - :language: yaml - -We can now pack and deploy the new version of the Flask app: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:refresh-2nd-deployment] - :end-before: [docs:refresh-2nd-deployment-end] - :dedent: 2 - -Now let’s deploy PostgreSQL and integrate it with the Flask application: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:deploy-postgres] - :end-before: [docs:deploy-postgres-end] - :dedent: 2 - -Wait for ``juju status`` to show that the App is ``active`` again. -Running ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` -should still return the ``Hi!`` greeting. - -To check the total visitors, use -``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` -which should return ``1`` after the previous request to the root endpoint and -should be incremented each time the root endpoint is requested. - -If we perform another request to -``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1``, -``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` -will return ``2``. - -Tear things down ----------------- - -We’ve reached the end of this tutorial. We went through the entire -development process, including: - -- Creating a Flask application -- Deploying the application locally -- Building an OCI image using Rockcraft -- Packaging the application using Charmcraft -- Deplyoing the application using Juju -- Exposing the application using an ingress -- Adding an initial app and configuring the application -- Integrating the application with a database - -If you'd like to reset your working environment, you can run the following -in the rock directory ``flask-hello-world`` for the tutorial: - -.. literalinclude:: code/flask/task.yaml - :language: bash - :start-after: [docs:clean-environment] - :end-before: [docs:clean-environment-end] - :dedent: 2 - -You can also clean up your Multipass instance. Start by exiting it: - -.. code-block:: bash - - exit - -And then you can proceed with its deletion: - -.. code-block:: bash - - multipass delete charm-dev - multipass purge - -Next steps ----------- - -By the end of this tutorial you will have built a charm and evolved it -in a number of typical ways. But there is a lot more to explore: - -.. list-table:: - :widths: 30 30 - :header-rows: 1 - - * - If you are wondering... - - Visit... - * - "How do I...?" - - :ref:`How-to guides `, - :external+ops:ref:`Ops | How-to guides ` - * - "How do I debug?" - - `Charm debugging tools `_ - * - "How do I get in touch?" - - `Matrix channel `_ - * - "What is...?" - - :ref:`reference`, - :external+ops:ref:`Ops | Reference `, - :external+juju:ref:`Juju | Reference ` - * - "Why...?", "So what?" - - :external+ops:ref:`Ops | Explanation `, - :external+juju:ref:`Juju | Explanation ` From e2dacd449bc5a5b5eb92ebdaad972a48e56d2af5 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 10:23:10 -0500 Subject: [PATCH 27/51] docs: update flask tutorial title --- ...first-kubernetes-charm-for-a-flask-app.rst | 637 ++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst new file mode 100644 index 000000000..d0a053764 --- /dev/null +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst @@ -0,0 +1,637 @@ +================================================= +Write your first Kubernetes charm for a Flask app +================================================= + +Imagine you have a Flask application backed up by a database +such as PostgreSQL and need to deploy it. In a traditional setup, +this can be quite a challenge, but with Charmcraft you'll find +yourself packaging and deploying your Flask application in no time. +Let's get started! + +In this tutorial we will build a Kubernetes charm for a Flask +application using Charmcraft, so we can have a Flask application +up and running with Juju. + +This tutorial should take 90 minutes for you to complete. + +.. note:: + If you're new to the charming world: Flask applications are + specifically supported with a coordinated pair of profiles + for an OCI container image (**rock**) and corresponding + packaged software (**charm**) that allow for the application + to be deployed, integrated and operated on a Kubernetes + cluster with the Juju orchestration engine. + +What you'll need +---------------- + +- A workstation, e.g., a laptop, with amd64 or arm64 architecture which + has sufficient resources to launch a virtual machine with 4 CPUs, + 4 GB RAM, and a 50 GB disk +- Familiarity with Linux + +What you'll do +-------------- + +Create a Flask application. Use that to create a rock with +``rockcraft``. Use that to create a charm with ``charmcraft``. Use that +to test-deploy, configure, etc., your Flask application on a local +Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple +times, mimicking a real development process. + +Set things up +------------- + +.. include:: /reuse/tutorial/setup_stable.rst +.. |12FactorApp| replace:: Flask + +Let's create a new directory for this tutorial and change into it: + +.. code-block:: bash + + mkdir flask-hello-world + cd flask-hello-world + +Finally, install ``python3-venv`` and create a virtual environment: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:create-venv] + :end-before: [docs:create-venv-end] + :dedent: 2 + +Create the Flask application +---------------------------- + +Let's start by creating the "Hello, world" Flask application that +will be used for this tutorial. + +Create a ``requirements.txt`` file, copy the following text into it +and then save it: + +.. literalinclude:: code/flask/requirements.txt + +.. note:: + + The ``psycopg2-binary`` package is needed so the Flask application can + connect to PostgreSQL. + +Install the packages: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:install-requirements] + :end-before: [docs:install-requirements-end] + :dedent: 2 + +In the same directory, copy and save the following into a text file +called ``app.py``: + +.. literalinclude:: code/flask/app.py + :language: python + +Run the Flask application locally +--------------------------------- + +Now that we have a virtual environment with all the dependencies, let's +run the Flask application to verify that it works: + +.. code-block:: bash + + flask run -p 8000 + +Test the Flask application by using ``curl`` to send a request to the root +endpoint. You will need a new terminal for this; use +``multipass shell charm-dev`` to get another terminal: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:curl-flask] + :end-before: [docs:curl-flask-end] + :dedent: 2 + +The Flask application should respond with ``Hello, world!``. + +The Flask application looks good, so we can stop for now using +:kbd:`Ctrl` + :kbd:`C`. + +Pack the Flask application into a rock +-------------------------------------- + +First, we'll need a ``rockcraft.yaml`` file. Using the +``flask-framework`` profile, Rockcraft will automate the creation of +``rockcraft.yaml`` and tailor the file for a Flask application. +From the ``flask-hello-world`` directory, initialize the rock: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:create-rockcraft-yaml] + :end-before: [docs:create-rockcraft-yaml-end] + :dedent: 2 + +The ``rockcraft.yaml`` file will automatically be created and set the name +based on your working directory. Choosing a different name or running on +a platform different from ``amd64`` will influence the names of the files +generated by Rockcraft. + +Check out the contents of ``rockcraft.yaml``: + +.. code:: bash + + cat rockcraft.yaml + +The top of the file should look similar to the following snippet: + +.. code:: yaml + + name: flask-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Flask application + version: '0.1' # just for humans. Semantic versioning is recommended + summary: A summary of your Flask application # 79 char long summary + description: | + This is flask-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + + +Verify that the ``name`` is ``flask-hello-world``. + +Ensure that ``platforms`` includes the architecture of your host. Check +the architecture of your system: + +.. code-block:: bash + + dpkg --print-architecture + + +If your host uses the ARM architecture, open ``rockcraft.yaml`` in a +text editor and include ``arm64`` under ``platforms``. + +Now let's pack the rock: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:pack] + :end-before: [docs:pack-end] + :dedent: 2 + +Depending on your system and network, this step can take several +minutes to finish. Once Rockcraft has finished packing the Flask rock, +the terminal will respond with something similar to +``Packed flask-hello-world_0.1_amd64.rock``. + +.. note:: + + If you are not on an ``amd64`` platform, the name of the ``.rock`` file + will be different for you. + +The rock needs to be copied to the MicroK8s registry, which stores OCI +archives so they can be downloaded and deployed in the Kubernetes cluster. +Copy the rock: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:skopeo-copy] + :end-before: [docs:skopeo-copy-end] + :dedent: 2 + +.. seealso:: + + See more: `Ubuntu manpage | skopeo + `_ + +Create the charm +---------------- + +From the ``flask-hello-world`` directory, let's create a new directory +for the charm and change inside it: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:create-charm-dir] + :end-before: [docs:create-charm-dir-end] + :dedent: 2 + +Using the ``flask-framework`` profile, Charmcraft will automate the +creation of the files needed for our charm, including a +``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. +The source code contains the logic required to operate the Flask +application. + +Initialize a charm named ``flask-hello-world``: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:charm-init] + :end-before: [docs:charm-init-end] + :dedent: 2 + +The files will automatically be created in your working directory. +Let's pack the charm: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:charm-pack] + :end-before: [docs:charm-pack-end] + :dedent: 2 + +Depending on your system and network, this step can take several +minutes to finish. + +Once Charmcraft has finished packing the charm, the terminal will +respond with something similar to +``Pack flask-hello-world_ubuntu-24.04-amd64.charm``. + +.. note:: + + If you are not on the ``amd64`` platform, the name of the ``.charm`` + file will be different for you. + +Deploy the Flask application +---------------------------- + +A Juju model is needed to handle Kubernetes resources while deploying +the Flask application. Let's create a new model: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:add-juju-model] + :end-before: [docs:add-juju-model-end] + :dedent: 2 + +If you are not on a host with the ``amd64`` architecture, you will need to include +to include a constraint to the Juju model to specify your architecture. +Check the architecture of your system using +``dpkg --print-architecture``. + +Set the Juju model constraints using + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:add-model-constraints] + :end-before: [docs:add-model-constraints-end] + :dedent: 2 + +Now let’s use the OCI image we previously uploaded to deploy the Flask +application. Deploy using Juju by specifying the OCI image name with the +``--resource`` option: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:deploy-flask-app] + :end-before: [docs:deploy-flask-app-end] + :dedent: 2 + +It will take a few minutes to deploy the Flask application. You can monitor the +progress using + +.. code:: bash + + juju status --relations --watch 2s + +The ``--relations`` flag will list the currently enabled integrations. +It can take a couple of minutes for the apps to finish the deployment. +Once the status of the App has gone to ``active``, you can stop watching +using :kbd:`Ctrl` + :kbd:`C`. + +.. seealso:: + + See more: :external+juju:ref:`Juju | juju status ` + +The Flask application should now be running. We can monitor the status of +the deployment using ``juju status`` which should be similar to the +following output: + +.. terminal:: + + Model Controller Cloud/Region Version SLA Timestamp + flask-hello-world dev-controller microk8s/localhost 3.1.8 unsupported 17:04:11+10:00 + + App Version Status Scale Charm Channel Rev Address Exposed Message + flask-hello-world active 1 flask-hello-world 0 10.152.183.166 no + + Unit Workload Agent Address Ports Message + flask-hello-world/0* active idle 10.1.87.213 + +The deployment is finished when the status shows ``active``. + +Let's expose the application using ingress. Deploy the +``nginx-ingress-integrator`` charm and integrate it with the Flask app: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:deploy-nginx] + :end-before: [docs:deploy-nginx-end] + :dedent: 2 + +The hostname of the app needs to be defined so that it is accessible via +the ingress. We will also set the default route to be the root endpoint: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:config-nginx] + :end-before: [docs:config-nginx-end] + :dedent: 2 + +Monitor ``juju status`` until everything has a status of ``active``. + +Test the deployment using +``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` to send +a request via the ingress to the root endpoint. It should still be returning +the ``Hello, world!`` greeting. + +.. note:: + + The ``--resolve flask-hello-world:80:127.0.0.1`` option to the ``curl`` + command is a way of resolving the hostname of the request without + setting a DNS record. + +Configure the Flask application +------------------------------- + +To demonstrate how to provide a configuration to the Flask application, +we will make the greeting configurable. We will expect this +configuration option to be available in the Flask app configuration under the +keyword ``GREETING``. Change back to the ``flask-hello-world`` directory using +``cd ..`` and copy the following code into ``app.py``: + +.. literalinclude:: code/flask/greeting_app.py + :language: python + +Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code-block:: yaml + :emphasize-lines: 5 + + name: flask-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Flask application + version: '0.2' # just for humans. Semantic versioning is recommended + summary: A summary of your Flask application # 79 char long summary + description: | + This is flask-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Let's run the pack and upload commands for the rock: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:docker-update] + :end-before: [docs:docker-update-end] + :dedent: 2 + +Change back into the charm directory using ``cd charm``. + +The ``flask-framework`` Charmcraft extension supports adding +configurations to ``charmcraft.yaml`` which will be passed as +environment variables to the Flask application. Add the following to +the end of the ``charmcraft.yaml`` file: + +.. literalinclude:: code/flask/greeting_charmcraft.yaml + :language: yaml + +.. note:: + + Configuration options are automatically capitalized and ``-`` are replaced + by ``_``. A ``FLASK_`` prefix will also be added which will let Flask + identify which environment variables to include when running + ``app.config.from_prefixed_env()`` in ``app.py``. + +We can now pack and deploy the new version of the Flask app: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:refresh-deployment] + :end-before: [docs:refresh-deployment-end] + :dedent: 2 + +After we wait for a bit monitoring ``juju status`` the application +should go back to ``active`` again. Verify that +the new configuration has been added using +``juju config flask-hello-world | grep -A 6 greeting:`` which should show +the configuration option. + +.. note:: + + The ``grep`` command extracts a portion of the configuration to make + it easier to check whether the configuration option has been added. + +Using ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` +shows that the response is still ``Hello, world!`` as expected. + +Now let’s change the greeting: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:change-config] + :end-before: [docs:change-config-end] + :dedent: 2 + +After we wait for a moment for the app to be restarted, using +``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` +should now return the updated ``Hi!`` greeting. + +Integrate with a database +------------------------- + +Now let's keep track of how many visitors your application has received. +This will require integration with a database to keep the visitor count. +This will require a few changes: + +* We will need to create a database migration that creates the ``visitors`` table +* We will need to keep track how many times the root endpoint has been called + in the database +* We will need to add a new endpoint to retrieve the number of visitors from the + database + +Let's start with the database migration to create the required tables. +The charm created by the ``flask-framework`` extension will execute the +``migrate.py`` script if it exists. This script should ensure that the +database is initialized and ready to be used by the application. We will +create a ``migrate.py`` file containing this logic. + +Go back out to the ``flask-hello-world`` directory using ``cd ..``, +open the ``migrate.py`` file using a text editor and paste the +following code into it: + +.. literalinclude:: code/flask/visitors_migrate.py + :language: python + +.. note:: + + The charm will pass the Database connection string in the + ``POSTGRESQL_DB_CONNECT_STRING`` environment variable once + PostgreSQL has been integrated with the charm. + +Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code-block:: yaml + :emphasize-lines: 5 + + name: flask-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/1.6.0/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@22.04 # the base environment for this Flask application + version: '0.3' # just for humans. Semantic versioning is recommended + summary: A summary of your Flask application # 79 char long summary + description: | + This is flask-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +The app code also needs to be updated to keep track of the number of visitors +and to include a new endpoint to retrieve the number of visitors to the +app. Open ``app.py`` in a text editor and replace its contents with the +following code: + +.. collapse:: visitors_app.py + + .. literalinclude:: code/flask/visitors_app.py + :language: python + +Let's run the pack and upload commands for the rock: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:docker-2nd-update] + :end-before: [docs:docker-2nd-update-end] + :dedent: 2 + +Change back into the charm directory using ``cd charm``. + +The Flask app now requires a database which needs to be declared in the +``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in a text editor and +add the following section to the end: + +.. literalinclude:: code/flask/visitors_charmcraft.yaml + :language: yaml + +We can now pack and deploy the new version of the Flask app: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:refresh-2nd-deployment] + :end-before: [docs:refresh-2nd-deployment-end] + :dedent: 2 + +Now let’s deploy PostgreSQL and integrate it with the Flask application: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:deploy-postgres] + :end-before: [docs:deploy-postgres-end] + :dedent: 2 + +Wait for ``juju status`` to show that the App is ``active`` again. +Running ``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` +should still return the ``Hi!`` greeting. + +To check the total visitors, use +``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` +which should return ``1`` after the previous request to the root endpoint and +should be incremented each time the root endpoint is requested. + +If we perform another request to +``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1``, +``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` +will return ``2``. + +Tear things down +---------------- + +We’ve reached the end of this tutorial. We went through the entire +development process, including: + +- Creating a Flask application +- Deploying the application locally +- Building an OCI image using Rockcraft +- Packaging the application using Charmcraft +- Deplyoing the application using Juju +- Exposing the application using an ingress +- Adding an initial app and configuring the application +- Integrating the application with a database + +If you'd like to reset your working environment, you can run the following +in the rock directory ``flask-hello-world`` for the tutorial: + +.. literalinclude:: code/flask/task.yaml + :language: bash + :start-after: [docs:clean-environment] + :end-before: [docs:clean-environment-end] + :dedent: 2 + +You can also clean up your Multipass instance. Start by exiting it: + +.. code-block:: bash + + exit + +And then you can proceed with its deletion: + +.. code-block:: bash + + multipass delete charm-dev + multipass purge + +Next steps +---------- + +By the end of this tutorial you will have built a charm and evolved it +in a number of typical ways. But there is a lot more to explore: + +.. list-table:: + :widths: 30 30 + :header-rows: 1 + + * - If you are wondering... + - Visit... + * - "How do I...?" + - :ref:`How-to guides `, + :external+ops:ref:`Ops | How-to guides ` + * - "How do I debug?" + - `Charm debugging tools `_ + * - "How do I get in touch?" + - `Matrix channel `_ + * - "What is...?" + - :ref:`reference`, + :external+ops:ref:`Ops | Reference `, + :external+juju:ref:`Juju | Reference ` + * - "Why...?", "So what?" + - :external+ops:ref:`Ops | Explanation `, + :external+juju:ref:`Juju | Explanation ` From 06385ccad6c6568db9d9a9930643555405078eb5 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 13:06:42 -0500 Subject: [PATCH 28/51] fix: sync Flask and Django tutorials --- docs/tutorial/code/flask/task.yaml | 4 +- ...irst-kubernetes-charm-for-a-django-app.rst | 25 +++++++---- ...first-kubernetes-charm-for-a-flask-app.rst | 43 +++++++++++-------- spread.yaml | 2 +- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 2e69e9bb2..3ba1f7eda 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -196,8 +196,8 @@ execute: | # [docs:deploy-postgres-end] # give Juju some time to deploy and refresh the apps - juju wait-for application postgresql-k8s --query='status=="active"' --timeout 30m - juju wait-for application flask-hello-world --query='status=="active"' --timeout 30m | juju status --relations + juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m | juju status --relations + juju wait-for application flask-hello-world --query='status=="active"' --timeout 10m | juju status --relations juju status --relations diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst index efdbf8a8c..b0cc4eb1d 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-django-app.rst @@ -36,7 +36,7 @@ What you’ll do Create a Django application. Use that to create a rock with ``rockcraft``. Use that to create a charm with ``charmcraft``. Use that -to test-deploy, configure, etc., your Django application on a local +to test, deploy, configure, etc., your Django application on a local Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple times, mimicking a real development process. @@ -70,6 +70,9 @@ Finally, install ``python3-venv`` and create a virtual environment: Create the Django application ----------------------------- +Let's start by creating the "Hello, world" Django application that +will be used for this tutorial. + Create a ``requirements.txt`` file, copy the following text into it and then save it: @@ -148,8 +151,8 @@ browser. Open a new tab and visit The Django application should respond in the browser with ``The install worked successfully! Congratulations!``. -The Django application looks good, so you can stop it for now in the -Multipass VM using :kbd:`Ctrl` + :kbd:`C`. +The Django application looks good, so we can stop it for now from the +original terminal of the Multipass VM using :kbd:`Ctrl` + :kbd:`C`. Pack the Django application into a rock --------------------------------------- @@ -291,7 +294,9 @@ Now let’s pack the rock: in the pack command for older versions of Rockcraft. Depending on your system and network, this step can take several minutes to -finish. Once Rockcraft has finished packing the Django rock, the +finish. + +Once Rockcraft has finished packing the Django rock, the terminal will respond with something similar to ``Packed django-hello-world_0.1_amd64.rock``. @@ -343,9 +348,9 @@ Initialize a charm named ``django-hello-world``: The files will automatically be created in your working directory. -We will need to connect to the PostgreSQL database. Open the -``charmcraft.yaml`` file and add the following section to the end of the -file: +We will need to connect the Django application to the PostgreSQL database. +Open the ``charmcraft.yaml`` file and add the following section to the end +of the file: .. literalinclude:: code/django/postgres_requires_charmcraft.yaml :language: yaml @@ -389,7 +394,7 @@ the Django application. Let’s create a new model: If you are not on a host with the ``amd64`` architecture, you will need to include a constraint to the Juju model to specify your architecture. -Check the architecture of your system using +You can check the architecture of your system using ``dpkg --print-architecture``. Set the Juju model constraints using @@ -640,7 +645,9 @@ Enable a configuration ---------------------- To demonstrate how to provide a configuration to the Django application, -we will make the greeting configurable. Go back out to the rock +we will make the greeting configurable. We will expect this +configuration option to be available in the Django app configuration under the +keyword ``GREETING``. Go back out to the rock directory ``django-hello-world`` using ``cd ..``. From there, open the ``django_hello_world/greeting/views.py`` file and replace the content with: diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst index d0a053764..a17db5a81 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst @@ -1,4 +1,5 @@ -================================================= +.. _write-your-first-kubernetes-charm-for-a-flask-app: + Write your first Kubernetes charm for a Flask app ================================================= @@ -27,18 +28,24 @@ What you'll need - A workstation, e.g., a laptop, with amd64 or arm64 architecture which has sufficient resources to launch a virtual machine with 4 CPUs, - 4 GB RAM, and a 50 GB disk -- Familiarity with Linux + 4 GB RAM, and a 50 GB disk. +- Familiarity with Linux. What you'll do -------------- Create a Flask application. Use that to create a rock with ``rockcraft``. Use that to create a charm with ``charmcraft``. Use that -to test-deploy, configure, etc., your Flask application on a local +to test, deploy, configure, etc., your Flask application on a local Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple times, mimicking a real development process. +.. important:: + + Should you get stuck or notice issues, please get in touch on + `Matrix `_ or + `Discourse `_ + Set things up ------------- @@ -102,7 +109,7 @@ run the Flask application to verify that it works: Test the Flask application by using ``curl`` to send a request to the root endpoint. You will need a new terminal for this; use -``multipass shell charm-dev`` to get another terminal: +``multipass shell charm-dev`` to open a new terminal in Multipass: .. literalinclude:: code/flask/task.yaml :language: bash @@ -112,8 +119,8 @@ endpoint. You will need a new terminal for this; use The Flask application should respond with ``Hello, world!``. -The Flask application looks good, so we can stop for now using -:kbd:`Ctrl` + :kbd:`C`. +The Flask application looks good, so we can stop it for now from the +original terminal using :kbd:`Ctrl` + :kbd:`C`. Pack the Flask application into a rock -------------------------------------- @@ -130,9 +137,7 @@ From the ``flask-hello-world`` directory, initialize the rock: :dedent: 2 The ``rockcraft.yaml`` file will automatically be created and set the name -based on your working directory. Choosing a different name or running on -a platform different from ``amd64`` will influence the names of the files -generated by Rockcraft. +based on your working directory. Check out the contents of ``rockcraft.yaml``: @@ -188,7 +193,9 @@ Now let's pack the rock: :dedent: 2 Depending on your system and network, this step can take several -minutes to finish. Once Rockcraft has finished packing the Flask rock, +minutes to finish. + +Once Rockcraft has finished packing the Flask rock, the terminal will respond with something similar to ``Packed flask-hello-world_0.1_amd64.rock``. @@ -252,7 +259,7 @@ minutes to finish. Once Charmcraft has finished packing the charm, the terminal will respond with something similar to -``Pack flask-hello-world_ubuntu-24.04-amd64.charm``. +``Packed flask-hello-world_ubuntu-24.04-amd64.charm``. .. note:: @@ -273,7 +280,7 @@ the Flask application. Let's create a new model: If you are not on a host with the ``amd64`` architecture, you will need to include to include a constraint to the Juju model to specify your architecture. -Check the architecture of your system using +You can check the architecture of your system using ``dpkg --print-architecture``. Set the Juju model constraints using @@ -315,6 +322,7 @@ the deployment using ``juju status`` which should be similar to the following output: .. terminal:: + :input: juju status Model Controller Cloud/Region Version SLA Timestamp flask-hello-world dev-controller microk8s/localhost 3.1.8 unsupported 17:04:11+10:00 @@ -325,8 +333,6 @@ following output: Unit Workload Agent Address Ports Message flask-hello-world/0* active idle 10.1.87.213 -The deployment is finished when the status shows ``active``. - Let's expose the application using ingress. Deploy the ``nginx-ingress-integrator`` charm and integrate it with the Flask app: @@ -418,9 +424,8 @@ the end of the ``charmcraft.yaml`` file: .. note:: Configuration options are automatically capitalized and ``-`` are replaced - by ``_``. A ``FLASK_`` prefix will also be added which will let Flask - identify which environment variables to include when running - ``app.config.from_prefixed_env()`` in ``app.py``. + by ``_``. A ``FLASK_`` prefix will also be added as a namespace + for app configurations. We can now pack and deploy the new version of the Flask app: @@ -584,7 +589,7 @@ development process, including: - Packaging the application using Charmcraft - Deplyoing the application using Juju - Exposing the application using an ingress -- Adding an initial app and configuring the application +- Configuring the application - Integrating the application with a database If you'd like to reset your working environment, you can run the following diff --git a/spread.yaml b/spread.yaml index 9eb07fabc..c8357f041 100644 --- a/spread.yaml +++ b/spread.yaml @@ -23,7 +23,7 @@ backends: google: key: '$(HOST: echo "$SPREAD_GOOGLE_KEY")' location: snapd-spread/us-east1-b - halt-timeout: 2h + halt-timeout: 3h systems: - ubuntu-18.04-64: workers: 1 From 030bad6b66e9b592cbb8da1b94b609dafbfaa43d Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 15:38:52 -0500 Subject: [PATCH 29/51] test: update spread.yaml --- spread.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread.yaml b/spread.yaml index c8357f041..f83417807 100644 --- a/spread.yaml +++ b/spread.yaml @@ -138,7 +138,7 @@ suites: docs/tutorial/code/: summary: tests tutorial from the docs systems: - - ubuntu-22.04-64 + - ubuntu-24.04-64 #manual: true tests/spread/commands/: summary: simple charmcraft commands From 869913982cf2f40f84f22bdd8cca4a35f8cba459 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 15:42:39 -0500 Subject: [PATCH 30/51] docs: update fastapi tutorial --- ...rst-kubernetes-charm-for-a-fastapi-app.rst | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index 12a8c0e9e..d0f6c3512 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -4,43 +4,43 @@ Write your first Kubernetes charm for a FastAPI app =================================================== +Imagine you have a FastAPI application backed up by a database +such as PostgreSQL and need to deploy it. In a traditional setup, +this can be quite a challenge, but with Charmcraft you'll find +yourself packaging and deploying your FastAPI application in no time. +Let's get started! + +In this tutorial we will build a Kubernetes charm for a FastAPI +application using Charmcraft, so we can have a FastAPI application +up and running with Juju. + +This tutorial should take 90 minutes for you to complete. + +.. note:: + If you're new to the charming world: Flask applications are + specifically supported with a coordinated pair of profiles + for an OCI container image (**rock**) and corresponding + packaged software (**charm**) that allow for the application + to be deployed, integrated and operated on a Kubernetes + cluster with the Juju orchestration engine. What you'll need ---------------- -- A working station, e.g., a laptop, with amd64 architecture which has - sufficient resources to launch a virtual machine with 4 CPUs, 4GB RAM, - and a 50GB disk. - - * Note that a workstation with arm64 architecture can complete the - majority of this tutorial. +- A workstation, e.g., a laptop, with amd64 or arm64 architecture which + has sufficient resources to launch a virtual machine with 4 CPUs, + 4 GB RAM, and a 50 GB disk. - Familiarity with Linux. -- About 90 minutes of free time. What you'll do -------------- -Create a FastAPI application. Use that to create a rock with ``rockcraft``. Use -that to create a charm with ``charmcraft``. Use that to test-deploy, configure, etc., -your Django application on a local Kubernetes cloud, ``microk8s``, with ``juju``. -All of that multiple times, mimicking a real development process. - -.. note:: - - **rock** - - An Ubuntu LTS-based OCI compatible container image designed to meet security, - stability, and reliability requirements for cloud-native software. - - **charm** - - A package consisting of YAML files + Python code that will automate every - aspect of an application's lifecycle so it can be easily orchestrated with Juju. - - **Juju** - - An orchestration engine for charmed applications. +Create a FastAPI application. Use that to create a rock with +``rockcraft``. Use that to create a charm with ``charmcraft``. Use that +to test, deploy, configure, etc., your FastAPI application on a local +Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple +times, mimicking a real development process. .. important:: From 5019f95d11d5a0bd7fe725790053ab6de717f77d Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 15:48:38 -0500 Subject: [PATCH 31/51] test: update flask spread test --- docs/tutorial/code/flask/task.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 3ba1f7eda..f2e098773 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -196,8 +196,8 @@ execute: | # [docs:deploy-postgres-end] # give Juju some time to deploy and refresh the apps - juju wait-for application postgresql-k8s --query='status=="active"' --timeout 10m | juju status --relations - juju wait-for application flask-hello-world --query='status=="active"' --timeout 10m | juju status --relations + juju wait-for application postgresql-k8s --query='status=="active"' --timeout 20m | juju status --relations + juju wait-for application flask-hello-world --query='status=="active"' --timeout 20m | juju status --relations juju status --relations From 7d8b696b33ab55d78370d6f6d4b39418f12ee2d2 Mon Sep 17 00:00:00 2001 From: erinecon Date: Wed, 29 Jan 2025 15:51:41 -0500 Subject: [PATCH 32/51] docs: update fastapi tutorial --- ...rst-kubernetes-charm-for-a-fastapi-app.rst | 91 ++++--------------- 1 file changed, 16 insertions(+), 75 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index d0f6c3512..fbd033698 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -52,78 +52,8 @@ times, mimicking a real development process. Set things up ------------- -Install Multipass. - - See more: `Multipass | How to install Multipass - `_ - -Use Multipass to launch an Ubuntu VM with the name charm-dev from the 24.04 blueprint: - -.. code-block:: bash - - multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 24.04 - -Once the VM is up, open a shell into it: - -.. code-block:: bash - - multipass shell charm-dev - -In order to create the rock, you'll need to install Rockcraft: - -.. code-block:: bash - - sudo snap install rockcraft --channel latest/edge --classic - -``LXD`` will be required for building the rock. Make sure it is installed -and initialised: - -.. code-block:: bash - - sudo snap install lxd - lxd init --auto - -In order to create the charm, you'll need to install Charmcraft: - -.. code-block:: bash - - sudo snap install charmcraft --channel latest/edge --classic - -MicroK8s is required to deploy the FastAPI application on Kubernetes. -Install MicroK8s: - -.. code-block:: bash - - sudo snap install microk8s --channel 1.31-strict/stable - sudo adduser $USER snap_microk8s - newgrp snap_microk8s - -Wait for MicroK8s to be ready using ``sudo microk8s status --wait-ready``. -Several MicroK8s add-ons are required for deployment: - -.. code-block:: bash - - sudo microk8s enable hostpath-storage - # Required to host the OCI image of the FastAPI application - sudo microk8s enable registry - # Required to expose the FastAPI application - sudo microk8s enable ingress - -Juju is required to deploy the FastAPI application. Install Juju and bootstrap -a development controller: - -.. code-block:: bash - - sudo snap install juju --channel 3.5/stable - mkdir -p ~/.local/share - juju bootstrap microk8s dev-controller - -Finally, create a new directory for this tutorial and go inside it: - -.. code-block:: bash - - mkdir fastapi-hello-world - cd fastapi-hello-world +.. include:: /reuse/tutorial/setup_edge.rst +.. |12FactorApp| replace:: FastAPI .. note:: @@ -139,6 +69,20 @@ Finally, create a new directory for this tutorial and go inside it: ``sudo snap refresh rockcraft --channel latest/edge`` to get the latest edge version of Rockcraft. +Let's create a directory for this tutorial and change into it: + +.. code-block:: bash + + mkdir fastapi-hello-world + cd fastapi-hello-world + +Finally, install ``python-venv`` and create a virtual environment: + +.. code-block:: bash + + sudo apt-get update && sudo apt-get install python3-venv -y + python3 -m venv .venv + source .venv/bin/activate Create the FastAPI application ------------------------------ @@ -172,9 +116,6 @@ Install ``python3-venv`` and create a virtual environment: .. code-block:: bash - sudo apt-get update && sudo apt-get install python3-venv -y - python3 -m venv .venv - source .venv/bin/activate pip install -r requirements.txt Now that we have a virtual environment with all the dependencies, From 0036d3a4f9fe54eaec4ac3e18cc2c04d5c532332 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 30 Jan 2025 14:14:04 -0500 Subject: [PATCH 33/51] docs: update fastapi tutorial --- ...rst-kubernetes-charm-for-a-fastapi-app.rst | 434 ++++++++++++------ ...first-kubernetes-charm-for-a-flask-app.rst | 26 +- 2 files changed, 304 insertions(+), 156 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index fbd033698..9729fc9e8 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -90,13 +90,27 @@ Create the FastAPI application Start by creating the "Hello, world" FastAPI application that will be used for this tutorial. -Create a ``requirements.txt`` file, copy the following text into it and then save it: +Create a ``requirements.txt`` file, copy the following text into it +and then save it: .. code-block:: bash fastapi[standard] + psycopg2-binary -In the same directory, copy and save the following into a text file called ``app.py``: +.. note:: + + The ``psycopg2-binary`` package is needed so the Flask application can + connect to PostgreSQL. + +Install the packages: + +.. code-block:: bash + + pip install -r requirements.txt + +In the same directory, copy and save the following into a text file +called ``app.py``: .. code-block:: python @@ -112,12 +126,6 @@ In the same directory, copy and save the following into a text file called ``app Run the FastAPI application locally ----------------------------------- -Install ``python3-venv`` and create a virtual environment: - -.. code-block:: bash - - pip install -r requirements.txt - Now that we have a virtual environment with all the dependencies, let's run the FastAPI application to verify that it works: @@ -126,42 +134,78 @@ let's run the FastAPI application to verify that it works: fastapi dev app.py --port 8080 Test the FastAPI application by using ``curl`` to send a request to the root -endpoint. You may need a new terminal for this; if you are using Multipass, use -``multipass shell charm-dev`` to get another terminal: +endpoint. You will need a new terminal for this; use +``multipass shell charm-dev`` to open a new terminal in Multipass: .. code-block:: bash curl localhost:8080 -The FastAPI application should respond with ``{"message":"Hello World"}``. The -FastAPI application looks good, so we can stop for now using :kbd:`Ctrl` + -:kbd:`C`. +The FastAPI application should respond with ``{"message":"Hello World"}``. + +The FastAPI application looks good, so we can stop for now from the +original terminal using :kbd:`Ctrl` + :kbd:`C`. Pack the FastAPI application into a rock ---------------------------------------- -First, we'll need a ``rockcraft.yaml`` file. Rockcraft will automate its creation -and tailoring for a FastAPI application by using the ``fastapi-framework`` profile: +First, we'll need a ``rockcraft.yaml`` file. Using the +``fastapi-framework`` profile, Rockcraft will automate the creation of +``rockcraft.yaml`` and tailor the file for a FastAPI application. +From the ``fastapi-hello-world`` directory, initialize the rock: .. code-block:: bash rockcraft init --profile fastapi-framework -The ``rockcraft.yaml`` file will be automatically created, with its name being -set based on your working directory. Open the file in a text editor and ensure -that the ``name`` is ``fastapi-hello-world`` and that ``platforms`` includes -the architecture of your host. For example, if your host uses the ARM -architecture, include ``arm64`` in ``platforms``. +The ``rockcraft.yaml`` file will be automatically created, with the name being +set based on your working directory. -.. note:: +Check out the contents of ``rockcraft.yaml``: + +.. code:: bash - For this tutorial, we'll use the name ``fastapi-hello-world`` and assume that - you are on the ``amd64`` platform. Check the architecture of your system using - ``dpkg --print-architecture``. Choosing a different name or running on a - different platform will influence the names of the files generated by Rockcraft. + cat rockcraft.yaml -Pack the rock: +The top of the file should look similar to the following snippet: + +.. code:: yaml + + name: fastapi-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@24.04 # the base environment for this FastAPI application + version: '0.1' # just for humans. Semantic versioning is recommended + summary: A summary of your FastAPI application # 79 char long summary + description: | + This is fastapi project's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Verify that the ``name`` is ``fastapi-hello-world``. + +Ensure that ``platforms`` includes the architecture of your host. Check +the architecture of your system: + +.. code-block:: bash + + dpkg --print-architecture + +If your host uses the ARM architecture, open ``rockcraft.yaml`` in a +text editor and include ``arm64`` in ``platforms``. + +Now let's pack the rock: .. code-block:: bash @@ -169,27 +213,24 @@ Pack the rock: .. note:: - Depending on your system and network, this step can take a couple of minutes - to finish. - ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required while the FastAPI extension is experimental. -Once Rockcraft has finished packing the FastAPI rock, you'll find a new file -in your working directory with the ``.rock`` extension. View its contents: - -.. code-block:: bash +Depending on your system and network, this step can take several +minutes to finish. - ls *.rock -l +Once Rockcraft has finished packing the FastAPI rock, +the terminal will respond with something similar to +``Packed fastapi-hello-world_0.1_amd64.rock``. .. note:: - If you changed the ``name`` or ``version`` in ``rockcraft.yaml`` or are not - on the ``amd64`` platform, the name of the ``.rock`` file will be different - for you. + If you are not on the ``amd64`` platform, the name of the ``.rock`` file + will be different for you. -The rock needs to be copied to the MicroK8s registry so that it can be deployed -in the Kubernetes cluster: +The rock needs to be copied to the MicroK8s registry, which stores OCI +archives so they can be downloaded and deployed in the Kubernetes cluster. +Copy the rock: .. code-block:: bash @@ -197,26 +238,37 @@ in the Kubernetes cluster: oci-archive:fastapi-hello-world_0.1_amd64.rock \ docker://localhost:32000/fastapi-hello-world:0.1 +.. seealso:: + + See more: `Ubuntu manpage | skopeo + `_ + Create the charm ---------------- -Create a new directory for the charm and go inside it: +From the ``fastapi-hello-world`` direcotyr, let's create a new directory +for the charm and change inside it: .. code-block:: bash mkdir charm cd charm -We'll need a ``charmcraft.yaml``, ``requirements.txt`` and source code for the -charm. The source code contains the logic required to operate the FastAPI application. -Charmcraft will automate the creation of these files by using the -``fastapi-framework`` profile: +Using the ``fastapi-framework`` profile, Charmcraft will automate the +creation of the files needed for our charm, including a +``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. +The source code contains the logic required to operate the FastAPI +application. + +Initialize a charm named ``fastapi-hello-world``: .. code-block:: bash charmcraft init --profile fastapi-framework --name fastapi-hello-world +The files will automatically be created in your working directory. + The charm depends on several libraries. Download the libraries and pack the charm: .. code-block:: bash @@ -226,54 +278,69 @@ The charm depends on several libraries. Download the libraries and pack the char .. note:: - Depending on your system and network, this step may take a couple of minutes - to finish. - ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required while the FastAPI extension is experimental. -Once Charmcraft has finished packing the charm, you'll find a new file in your -working directory with the ``.charm`` extension. View its contents: - -.. code-block:: bash +Depending on your system and network, this step may take several +minutes to finish. - ls *.charm -l +Once Charmcraft has finished packing the charm, the terminal will +respond with something similar to +``Packed fastapi-hello-world_ubuntu-24.04-amd64.charm``. .. note:: - If you changed the name in ``charmcraft.yaml`` or are not on the ``amd64`` - platform, the name of the ``.charm`` file will be different for you. + If you are not on the ``amd64`` platform, the name of the ``.charm`` + file will be different for you. Deploy the FastAPI application ------------------------------ -A Juju model is needed to deploy the application. Let's create a new model: +A Juju model is needed to handle Kubernetes resources while deploying +the FastAPI application. Let's create a new model: .. code-block:: bash juju add-model fastapi-hello-world -.. note:: +If you are not on a host with the ``amd64`` architecture, you will +need to include a constraint to the Juju model to specify your +architecture. You can check the architecture of your system using +``dpkg --print-architecture``. + +Set the Juju model constraints using + +.. code-block:: bash - If you are not on a host with the ``amd64`` architecture, you will - need to include a constraint to the Juju model to specify your - architecture. For example, using the ``arm64`` architecture, you - would use ``juju set-model-constraints -m django-hello-world arch=arm64``. - Check the architecture of your system using ``dpkg --print-architecture``. + juju set-model-constraints -m fastapi-hello-world \ + arch=$(dpkg --print-architecture) -Now the FastAPI application can be deployed using Juju: + +Now let’s use the OCI image we previously uploaded to deploy the FastAPI +application. Deploy using Juju by specifying the OCI image name with the +``--resource`` option: .. code-block:: bash juju deploy ./fastapi-hello-world_amd64.charm fastapi-hello-world \ --resource app-image=localhost:32000/fastapi-hello-world:0.1 -.. note:: +It will take a few minutes to deploy the FastAPI application. You can monitor +the progress using + +.. code:: bash + + juju status --watch 2s - It will take a few minutes to deploy the FastAPI application. You can monitor - the progress using ``juju status --watch 5s``. Once the status of the app - changes to ``active``, you can stop watching using :kbd:`Ctrl` + :kbd:`C`. + +It can take a couple of minutes for the app to finish the deployment. +Once the status of the App has gone to ``active``, you can stop watching +using :kbd:`Ctrl` + :kbd:`C`. + +.. seealso:: + + See more: :external+juju:ref:`Juju | juju status ` The FastAPI application should now be running. We can monitor the status of the deployment using ``juju status``, which should be similar to the following @@ -291,9 +358,8 @@ output: Unit Workload Agent Address Ports Message fastapi-hello-world/0* active idle 10.1.157.75 -The deployment is finished when the status shows ``active``. Let's expose the -application using ingress. Deploy the ``nginx-ingress-integrator`` charm and -integrate it with the FastAPI app: +Let's expose the application using ingress. Deploy the +``nginx-ingress-integrator`` charm and integrate it with the FastAPI app: .. code-block:: bash @@ -301,17 +367,19 @@ integrate it with the FastAPI app: juju integrate nginx-ingress-integrator fastapi-hello-world The hostname of the app needs to be defined so that it is accessible via -the ingress. We will also set the default route to be the endpoint: +the ingress. We will also set the default route to be the root endpoint: .. code-block:: bash juju config nginx-ingress-integrator \ service-hostname=fastapi-hello-world path-routes=/ -Monitor ``juju status`` until everything has a status of ``active``. Use +Monitor ``juju status`` until everything has a status of ``active``. + +Test the deployment using ``curl http://fastapi-hello-world --resolve fast-api-hello-world:80:127.0.0.1`` -to send a request via the ingress. It should return the ``{"message":"Hello World"}`` -greeting. +to send a request via the ingress. It should return the +``{"message":"Hello World"}`` greeting. .. note:: @@ -323,10 +391,11 @@ greeting. Configure the FastAPI application --------------------------------- -Let's customise the greeting using a configuration option. We will expect this -configuration option to be available in the environment variable ``APP_GREETING``. -Go back out to the root directory of the project using ``cd ..`` and copy the -following code into ``app.py``: +To demonstrate how to provide a configuration to the Flask application, +we will make the greeting configurable. We will expect this +configuration option to be available in the FastAPI app configuration under the +keyword ``APP_GREETING``. Change back to the ``fastapi-hello-world`` directory +using ``cd ..`` and copy the following code into ``app.py``: .. code-block:: python @@ -340,19 +409,47 @@ following code into ``app.py``: async def root(): return {"message": os.getenv("APP_GREETING", "Hello World")} -Open ``rockcraft.yaml`` and update the version to ``0.2``. Run -``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack`` again, -then upload the new OCI image to the MicroK8s registry: +Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code-block:: yaml + :emphasize-lines: 5 + + name: fastapi-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@24.04 # the base environment for this FastAPI application + version: '0.2' # just for humans. Semantic versioning is recommended + summary: A summary of your FastAPI application # 79 char long summary + description: | + This is fastapi project's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Let's run the pack and upload commands for the rock: .. code-block:: bash + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:fastapi-hello-world_0.2_amd64.rock \ docker://localhost:32000/fastapi-hello-world:0.2 -Change back into the charm directory using ``cd charm``. The ``fastapi-framework`` -Charmcraft extension supports adding configurations to ``charmcraft.yaml`` which -will be passed as environment variables to the FastAPI application. Add the +Change back into the charm directory using ``cd charm``. + +The ``fastapi-framework`` Charmcraft extension supports adding +configurations to ``charmcraft.yaml`` which will be passed as +environment variables to the FastAPI application. Add the following to the end of the ``charmcraft.yaml`` file: .. code-block:: yaml @@ -367,50 +464,49 @@ following to the end of the ``charmcraft.yaml`` file: .. note:: - Configuration options are automatically capitalised and dashes are replaced by - underscores. An ``APP_`` prefix will also be added to ensure that environment - variables are namespaced. + Configuration options are automatically capitalized and ``-`` are replaced + by ``_``. An ``APP_`` prefix will also be added as a namespace + for app configurations. -Run ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack`` again. The -deployment can now be refreshed to make use of the new code: +We can now pack and deploy the new version of the FastAPI app: .. code-block:: bash + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack juju refresh fastapi-hello-world \ --path=./fastapi-hello-world_amd64.charm \ --resource app-image=localhost:32000/fastapi-hello-world:0.2 -Wait for ``juju status`` to show that the App is ``active`` again. Verify that the -new configuration has been added using ``juju config fastapi-hello-world | grep --A 6 greeting:`` which should show the configuration option. +After we wait for a bit monitoring ``juju status`` the application +should go back to ``active`` again. Verify that the +new configuration has been added using +``juju config fastapi-hello-world | grep -A 6 greeting:`` which should show +the configuration option. .. note:: The ``grep`` command extracts a portion of the configuration to make it easier to check whether the configuration option has been added. -Running ``http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` -shows that the response is still ``{"message":"Hello, world!"}`` as expected. The -greeting can be changed using Juju: +Using ``http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` +shows that the response is still ``{"message":"Hello, world!"}`` as expected. + +Now let's change the greeting: .. code-block:: bash juju config fastapi-hello-world greeting='Hi!' -``curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` now -returns the updated ``{"message":"Hi!"}`` greeting. - -.. note:: - - It may take a short time for the configuration to take effect. - +After we wait for a moment for the app to be restarted, using +``curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` +should now return the updated ``{"message":"Hi!"}`` greeting. Integrate with a database ------------------------- -Now let's keep track of how many visitors your application has received. This will -require integration with a database to keep the visitor count. This will require -a few changes: +Now let's keep track of how many visitors your application has received. +This will require integration with a database to keep the visitor count. +This will require a few changes: - We will need to create a database migration that creates the ``visitors`` table. - We will need to keep track of how many times the root endpoint has been called @@ -418,13 +514,15 @@ a few changes: - We will need to add a new endpoint to retrieve the number of visitors from the database. +Let's start with the database migration to create the required tables. The charm created by the ``fastapi-framework`` extension will execute the ``migrate.py`` script if it exists. This script should ensure that the -database is initialised and ready to be used by the application. We will create -a ``migrate.py`` file containing this logic. +database is initialized and ready to be used by the application. We will +create a ``migrate.py`` file containing this logic. -Go back out to the tutorial root directory using ``cd ..``. Create the -``migrate.py`` file using a text editor and paste the following code into it: +Go back out to the ``fastapi-hello-world`` directory using ``cd ..``, +open the ``migrate.py`` file using a text editor and paste the +following code into it: .. code-block:: python @@ -454,14 +552,37 @@ Go back out to the tutorial root directory using ``cd ..``. Create the ``POSTGRESQL_DB_CONNECT_STRING`` environment variable once postgres has been integrated with the charm. -Open the ``rockcraft.yaml`` file in a text editor and update the version -to ``0.3``. +Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: -To be able to connect to postgresql from the FastAPI app, the ``psycopg2-binary`` -dependency needs to be added in ``requirements.txt``. The app code also needs to -be updated to keep track of the number of visitors and to include a new endpoint -to retrieve the number of visitors. Open ``app.py`` in a text editor and replace -its contents with the following code: +.. code-block:: yaml + :emphasize-lines: 5 + + name: fastapi-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@24.04 # the base environment for this FastAPI application + version: '0.3' # just for humans. Semantic versioning is recommended + summary: A summary of your FastAPI application # 79 char long summary + description: | + This is fastapi project's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +The app code also needs to be updated to keep track of the number of visitors +and to include a new endpoint to retrieve the number of visitors to the +app. Open ``app.py`` in a text editor and replace its contents with the +following code: .. code-block:: python @@ -498,19 +619,20 @@ its contents with the following code: return {"count": total_visitors} -Run ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack`` and upload -the newly created rock to the MicroK8s registry: +Let's run the pack and upload commands for the rock: .. code-block:: bash + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:fastapi-hello-world_0.3_amd64.rock \ docker://localhost:32000/fastapi-hello-world:0.3 +Change back into the charm directory using ``cd charm``. + The FastAPI app now requires a database which needs to be declared in the -``charmcraft.yaml`` file. Go back into the charm directory using ``cd charm``. -Open ``charmcraft.yaml`` in a text editor and add the following section at the -end of the file: +``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in a text editor and +add the following section to the end: .. code-block:: yaml @@ -519,30 +641,32 @@ end of the file: interface: postgresql_client optional: false -Pack the charm using ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack`` -and refresh the deployment using Juju: +We can now pack and deploy the new version of the FastAPI app: .. code-block:: bash + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack juju refresh fastapi-hello-world \ --path=./fastapi-hello-world_amd64.charm \ --resource app-image=localhost:32000/fastapi-hello-world:0.3 -Deploy ``postgresql-k8s`` using Juju and integrate it with ``fastapi-hello-world``: +Now let’s deploy PostgreSQL and integrate it with the FastAPI application: .. code-block:: bash juju deploy postgresql-k8s --trust juju integrate fastapi-hello-world postgresql-k8s -Wait for ``juju status`` to show that the App is ``active`` again. Executing -``curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` should -still return the ``{"message":"Hi!"}`` greeting. +Wait for ``juju status`` to show that the App is ``active`` again. Running +``curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` +should still return the ``{"message":"Hi!"}`` greeting. -To check the local visitors, use ``curl http://fastapi-hello-world/visitors --resolve -fastapi-hello-world:80:127.0.0.1``, which should return ``{"count":1}`` after the -previous request to the root endpoint. This should be incremented each time the root -endpoint is requested. If we repeat this process, the output should be as follows: +To check the local visitors, use +``curl http://fastapi-hello-world/visitors +--resolve fastapi-hello-world:80:127.0.0.1``, which should return +``{"count":1}`` after the previous request to the root endpoint. This should +be incremented each time the root endpoint is requested. If we repeat +this process, the output should be as follows: .. terminal:: :input: curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1 @@ -555,11 +679,20 @@ endpoint is requested. If we repeat this process, the output should be as follow Tear things down ---------------- -We've reached the end of this tutorial. We have created a FastAPI application, -deployed it locally, integrated it with a database and exposed it via ingress! +We’ve reached the end of this tutorial. We went through the entire +development process, including: + +- Creating a FastAPI application +- Deploying the application locally +- Building an OCI image using Rockcraft +- Packaging the application using Charmcraft +- Deplyoing the application using Juju +- Exposing the application using an ingress +- Configuring the application +- Integrating the application with a database If you'd like to reset your working environment, you can run the following -in the root directory for the tutorial: +in the rock directory ``fastapi-hello-world`` for the tutorial: .. code-block:: bash @@ -573,8 +706,7 @@ in the root directory for the tutorial: # Remove the juju model juju destroy-model fastapi-hello-world --destroy-storage -If you created an instance using Multipass, you can also clean it up. -Start by exiting it: +You can also clean up your Multipass instance. Start by exiting it: .. code-block:: bash @@ -592,12 +724,26 @@ Next steps ---------- By the end of this tutorial, you will have built a charm and evolved it -in a number of practical ways, but there is a lot more to explore: - -+-------------------------+----------------------+ -| If you are wondering... | Visit... | -+=========================+======================+ -| "How do I...?" | :ref:`how-to-guides` | -+-------------------------+----------------------+ -| "What is...?" | :ref:`reference` | -+-------------------------+----------------------+ +in a number of typical ways, but there is a lot more to explore: + +.. list-table:: + :widths: 30 30 + :header-rows: 1 + + * - If you are wondering... + - Visit... + * - "How do I...?" + - :ref:`How-to guides `, + :external+ops:ref:`Ops | How-to guides ` + * - "How do I debug?" + - `Charm debugging tools `_ + * - "How do I get in touch?" + - `Matrix channel `_ + * - "What is...?" + - :ref:`reference`, + :external+ops:ref:`Ops | Reference `, + :external+juju:ref:`Juju | Reference ` + * - "Why...?", "So what?" + - :external+ops:ref:`Ops | Explanation `, + :external+juju:ref:`Juju | Explanation ` + diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst index a17db5a81..3535f0538 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst @@ -306,10 +306,9 @@ progress using .. code:: bash - juju status --relations --watch 2s + juju status --watch 2s -The ``--relations`` flag will list the currently enabled integrations. -It can take a couple of minutes for the apps to finish the deployment. +It can take a couple of minutes for the app to finish the deployment. Once the status of the App has gone to ``active``, you can stop watching using :kbd:`Ctrl` + :kbd:`C`. @@ -354,9 +353,9 @@ the ingress. We will also set the default route to be the root endpoint: Monitor ``juju status`` until everything has a status of ``active``. Test the deployment using -``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` to send -a request via the ingress to the root endpoint. It should still be returning -the ``Hello, world!`` greeting. +``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1`` +to send a request via the ingress. It should return the +``Hello, world!`` greeting. .. note:: @@ -569,13 +568,16 @@ should still return the ``Hi!`` greeting. To check the total visitors, use ``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` -which should return ``1`` after the previous request to the root endpoint and -should be incremented each time the root endpoint is requested. +which should return ``1`` after the previous request to the root endpoint, +This should be incremented each time the root endpoint is requested. If we +repeat this process, the output should be as follows: -If we perform another request to -``curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1``, -``curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1`` -will return ``2``. +.. terminal:: + :input: curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 + + Hi! + :input: curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 + 2 Tear things down ---------------- From 2edc39474b32c2b8f9a31b305067b91f836c1a26 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 30 Jan 2025 14:14:38 -0500 Subject: [PATCH 34/51] test: pin postgres revision in flask spread test --- docs/tutorial/code/flask/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index f2e098773..eddd51b64 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -191,7 +191,7 @@ execute: | # [docs:refresh-2nd-deployment-end] # [docs:deploy-postgres] - juju deploy postgresql-k8s --trust + juju deploy postgresql-k8s --trust --channel 14/stable --revision 460 juju integrate flask-hello-world postgresql-k8s # [docs:deploy-postgres-end] From 483ec4bc34d68d258b1bc0dfd32ff9d6237f7bb7 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 30 Jan 2025 14:17:55 -0500 Subject: [PATCH 35/51] fix: remove trailing whitespace --- .../write-your-first-kubernetes-charm-for-a-fastapi-app.rst | 2 +- .../write-your-first-kubernetes-charm-for-a-flask-app.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index 9729fc9e8..4693c150d 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -193,7 +193,7 @@ The top of the file should look similar to the following snippet: ... -Verify that the ``name`` is ``fastapi-hello-world``. +Verify that the ``name`` is ``fastapi-hello-world``. Ensure that ``platforms`` includes the architecture of your host. Check the architecture of your system: diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst index 3535f0538..02c7e6a5b 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst @@ -574,7 +574,7 @@ repeat this process, the output should be as follows: .. terminal:: :input: curl http://flask-hello-world --resolve flask-hello-world:80:127.0.0.1 - + Hi! :input: curl http://flask-hello-world/visitors --resolve flask-hello-world:80:127.0.0.1 2 From 79462b1080f7bf87c14012e1d3b54396e3b17c53 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 30 Jan 2025 14:25:35 -0500 Subject: [PATCH 36/51] fix: update tutorial index --- docs/tutorial/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 615f25a43..b8fb78ecb 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -15,6 +15,6 @@ Our tutorial comes in multiple flavours -- pick your flavour of choice! :maxdepth: 2 write-your-first-kubernetes-charm-for-a-django-app - flask write-your-first-kubernetes-charm-for-a-fastapi-app + write-your-first-kubernetes-charm-for-a-flask-app write-your-first-kubernetes-charm-for-a-go-app From 7e0a28f820fd7c5b15e2c189640942fb02f7c42e Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 30 Jan 2025 14:34:38 -0500 Subject: [PATCH 37/51] fix: update fastapi tutorial --- ...rst-kubernetes-charm-for-a-fastapi-app.rst | 134 ++++++++---------- 1 file changed, 60 insertions(+), 74 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index 4693c150d..c4162f29a 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -55,20 +55,6 @@ Set things up .. include:: /reuse/tutorial/setup_edge.rst .. |12FactorApp| replace:: FastAPI -.. note:: - - This tutorial requires version ``3.0.0`` or later of Charmcraft. Check which - version of Charmcraft you have installed using ``charmcraft --version``. If - you have an older version of Charmcraft installed, use - ``sudo snap refresh charmcraft --channel latest/edge`` to get the latest edge - version of Charmcraft. - - This tutorial requires version ``1.5.4`` or later of Rockcraft. Check which - version of Rockcraft you have installed using ``rockcraft --version``. If you - have an older version of Rockcraft installed, use - ``sudo snap refresh rockcraft --channel latest/edge`` to get the latest edge - version of Rockcraft. - Let's create a directory for this tutorial and change into it: .. code-block:: bash @@ -100,7 +86,7 @@ and then save it: .. note:: - The ``psycopg2-binary`` package is needed so the Flask application can + The ``psycopg2-binary`` package is needed so the FastAPI application can connect to PostgreSQL. Install the packages: @@ -172,24 +158,24 @@ The top of the file should look similar to the following snippet: .. code:: yaml - name: fastapi-hello-world - # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ - # for more information about bases and using 'bare' bases for chiselled rocks - base: ubuntu@24.04 # the base environment for this FastAPI application - version: '0.1' # just for humans. Semantic versioning is recommended - summary: A summary of your FastAPI application # 79 char long summary - description: | - This is fastapi project's description. You have a paragraph or two to tell the - most important story about it. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the - container registries out there. - # the platforms this rock should be built on and run on. - # you can check your architecture with `dpkg --print-architecture` - platforms: - amd64: - # arm64: - # ppc64el: - # s390x: + name: fastapi-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@24.04 # the base environment for this FastAPI application + version: '0.1' # just for humans. Semantic versioning is recommended + summary: A summary of your FastAPI application # 79 char long summary + description: | + This is fastapi project's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: ... @@ -391,7 +377,7 @@ to send a request via the ingress. It should return the Configure the FastAPI application --------------------------------- -To demonstrate how to provide a configuration to the Flask application, +To demonstrate how to provide a configuration to the FastAPI application, we will make the greeting configurable. We will expect this configuration option to be available in the FastAPI app configuration under the keyword ``APP_GREETING``. Change back to the ``fastapi-hello-world`` directory @@ -413,26 +399,26 @@ Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the top of the ``rockcraft.yaml`` file looks similar to the following: .. code-block:: yaml - :emphasize-lines: 5 - - name: fastapi-hello-world - # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ - # for more information about bases and using 'bare' bases for chiselled rocks - base: ubuntu@24.04 # the base environment for this FastAPI application - version: '0.2' # just for humans. Semantic versioning is recommended - summary: A summary of your FastAPI application # 79 char long summary - description: | - This is fastapi project's description. You have a paragraph or two to tell the - most important story about it. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the - container registries out there. - # the platforms this rock should be built on and run on. - # you can check your architecture with `dpkg --print-architecture` - platforms: - amd64: - # arm64: - # ppc64el: - # s390x: + :emphasize-lines: 5 + + name: fastapi-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@24.04 # the base environment for this FastAPI application + version: '0.2' # just for humans. Semantic versioning is recommended + summary: A summary of your FastAPI application # 79 char long summary + description: | + This is fastapi project's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: ... @@ -556,26 +542,26 @@ Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the top of the ``rockcraft.yaml`` file looks similar to the following: .. code-block:: yaml - :emphasize-lines: 5 - - name: fastapi-hello-world - # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ - # for more information about bases and using 'bare' bases for chiselled rocks - base: ubuntu@24.04 # the base environment for this FastAPI application - version: '0.3' # just for humans. Semantic versioning is recommended - summary: A summary of your FastAPI application # 79 char long summary - description: | - This is fastapi project's description. You have a paragraph or two to tell the - most important story about it. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the - container registries out there. - # the platforms this rock should be built on and run on. - # you can check your architecture with `dpkg --print-architecture` - platforms: - amd64: - # arm64: - # ppc64el: - # s390x: + :emphasize-lines: 5 + + name: fastapi-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: ubuntu@24.04 # the base environment for this FastAPI application + version: '0.3' # just for humans. Semantic versioning is recommended + summary: A summary of your FastAPI application # 79 char long summary + description: | + This is fastapi project's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: ... From 508ea8547e7049a841006aab2e41f5d7316da95d Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 07:52:28 -0500 Subject: [PATCH 38/51] fix: revert postgres pin and turn off spread --- docs/tutorial/code/flask/task.yaml | 2 +- spread.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index c4d538b29..8e2a5d9a0 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -177,7 +177,7 @@ execute: | # [docs:refresh-2nd-deployment-end] # [docs:deploy-postgres] - juju deploy postgresql-k8s --trust --channel 14/stable --revision 460 + juju deploy postgresql-k8s --trust juju integrate flask-hello-world postgresql-k8s # [docs:deploy-postgres-end] diff --git a/spread.yaml b/spread.yaml index eb81a94f5..089fea161 100644 --- a/spread.yaml +++ b/spread.yaml @@ -153,7 +153,7 @@ suites: summary: tests tutorial from the docs systems: - ubuntu-22.04-64 - #manual: true + manual: true prepare: | juju_channel=3.5/stable microk8s_channel=1.31-strict/stable From 85af6c144066f7c7bab7224621665a8a1f17cddc Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 09:10:06 -0500 Subject: [PATCH 39/51] test: output spread software versions --- docs/tutorial/code/flask/task.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 8e2a5d9a0..d23d1912d 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -16,6 +16,12 @@ execute: | mv *.yaml *.py *.txt $HOME cd $HOME + # Test/debug + microk8s version + juju version + charmcraft --version + rockcraft --version + # Don't use the staging store for this test unset CHARMCRAFT_STORE_API_URL unset CHARMCRAFT_UPLOAD_URL From 0a7a4865882abb086cb5e3d400f6b2a23a8f2d21 Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 11:47:17 -0500 Subject: [PATCH 40/51] docs: update go tutorial --- ...ur-first-kubernetes-charm-for-a-go-app.rst | 295 +++++++++--------- 1 file changed, 139 insertions(+), 156 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst index 4edf10e94..d0526da84 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst @@ -1,148 +1,80 @@ .. _write-your-first-kubernetes-charm-for-a-go-app: - Write your first Kubernetes charm for a Go app ============================================== +Imagine you have a Go application backed up by a database +such as PostgreSQL and need to deploy it. In a traditional setup, +this can be quite a challenge, but with Charmcraft you'll find +yourself packaging and deploying your Go application in no time. +Let's get started! + +In this tutorial we will build a Kubernetes charm for a Go +application using Charmcraft, so we can have a Go application +up and running with Juju. + +This tutorial should take 90 minutes for you to complete. + +.. note:: + If you're new to the charming world: Go applications are + specifically supported with a coordinated pair of profiles + for an OCI container image (**rock**) and corresponding + packaged software (**charm**) that allow for the application + to be deployed, integrated and operated on a Kubernetes + cluster with the Juju orchestration engine. What you'll need: ----------------- -- A working station, e.g., a laptop, with amd64 architecture which has sufficient - resources to launch a virtual machine with 4 CPUs, 4GB RAM, and a 50GB disk. - - * Note that a workstation with arm64 architecture can complete the majority of this - tutorial. +- A workstation, e.g., a laptop, with amd64 or arm64 architecture which + has sufficient resources to launch a virtual machine with 4 CPUs, + 4 GB RAM, and a 50 GB disk. - Familiarity with Linux. -- About 90 minutes of free time. - What you'll do: --------------- -Create a Go application. Use that to create a rock with ``rockcraft``. Use that to -create a charm with ``charmcraft``. Use that to test-deploy, configure, etc., your Go -application on a local Kubernetes cloud, ``microk8s``, with ``juju``. All of that -multiple, times, mimicking a real development process. - -.. note:: - - **rock** - - An Ubuntu LTS-based OCI compatible container image designed to meet security, - stability, and reliability requirements for cloud-native software. - - **charm** - - A package consisting of YAML files + Python code that will automate every aspect of - an application's lifecycle so it can be easily orchestrated with Juju. - - **Juju** - - An orchestration engine for charmed applications. +Create a Go application. Use that to create a rock with +``rockcraft``. Use that to create a charm with ``charmcraft``. Use that +to test, deploy, configure, etc., your Go application on a local +Kubernetes cloud, ``microk8s``, with ``juju``. All of that multiple +times, mimicking a real development process. .. important:: - Should you get stuck or notice issues, please get in touch on `Matrix - `_ or `Discourse - `_ + Should you get stuck or notice issues, please get in touch on + `Matrix `_ or + `Discourse `_ -Set things up: --------------- +Set things up +------------- -Install Multipass. +.. include:: /reuse/tutorial/setup_edge.rst +.. |12FactorApp| replace: Go - See more: `Multipass | How to install Multipass - `_ - -Use Multipass to launch an Ubuntu VM with the name ``charm-dev`` from the 22.04 -blueprint. +Finally, let's create a new directory for this tutorial and +change into it: .. code-block:: bash - multipass launch --cpus 4 --disk 50G --memory 4G --name charm-dev 22.04 - -Once the VM is up, open a shell into it: - -.. code-block:: bash - - multipass shell charm-dev - -In order to create the rock, you'll need to install Rockcraft: - -.. code-block:: bash - - sudo snap install rockcraft --classic - -``LXD`` will be required for building the rock. Make sure it is installed and -initialised: - -.. code-block:: bash - - sudo snap install lxd lxd init --auto - -In order to create the charm, you'll need to install Charmcraft: - -.. code-block:: bash - - sudo snap install charmcraft --channel latest/edge --classic - -MicroK8s is required to deploy the FastAPI application on Kubernetes. Install MicroK8s: - -.. code-block:: bash - - sudo snap install microk8s --channel 1.31-strict/stable sudo adduser $USER - snap_microk8s newgrp snap_microk8s - -Wait for MicroK8s to be ready using ``sudo microk8s status --wait-ready``. Several -MicroK8s add-ons are required for deployment: - -.. code-block:: bash - - sudo microk8s enable hostpath-storage # Required to host the OCI image of the - FastAPI application sudo microk8s enable registry # Required to expose the FastAPI - application sudo microk8s enable ingress - -Juju is required to deploy the Go application. Install Juju and bootstrap a development -controller: - -.. code-block:: bash - - sudo snap install juju --channel 3.5/stable mkdir -p ~/.local/share juju bootstrap - microk8s dev-controller - -Finally, create a new directory for this tutorial and go inside it: - -.. code-block:: bash - - mkdir go-hello-world cd go-hello-world - -.. note:: - - This tutorial requires version ``3.2.0`` or later of Charmcraft. Check which version - of Charmcraft you have installed using ``charmcraft --version``. If you have an - older version of Charmcraft installed, use ``sudo snap refresh charmcraft --channel - latest/edge`` to get the latest edge version of Charmcraft. - - This tutorial requires version ``1.5.4`` or later of Rockcraft. Check which version - of Rockcraft you have installed using ``rockcraft --version``. If you have an older - version of Rockcraft installed, use ``sudo snap refresh rockcraft --channel - latest/edge`` to get the latest edge version of Rockcraft. - + mkdir go-hello-world + cd go-hello-world Create the Go application ------------------------- -Start by creating the "Hello, world" Go application that will be used for this tutorial. +Start by creating the "Hello, world" Go application that will be +used for this tutorial. -Install ``go`` and initialise the Go module: +Install ``go`` and initialize the Go module: .. code-block:: bash sudo snap install go --classic go mod init go-hello-world -Create a ``main.go`` file, copy the following text into it and then save it: +Create a ``main.go`` file, copy the following text into it and then +save it: .. code-block:: python @@ -165,78 +97,119 @@ Create a ``main.go`` file, copy the following text into it and then save it: Run the Go application locally ------------------------------ -Build the Go application so it can be run: +First, we need to build the Go application so it can run: .. code-block:: bash go build . -Now that we have a binary compiled, let's run the Go application to verify that it -works: +Now that we have a binary compiled, let's run the Go application to verify +that it works: .. code-block:: bash ./go-hello-world -Test the Go application by using ``curl`` to send a request to the root endpoint. You -may need a new terminal for this; if you are using Multipass, use ``multipass shell -charm-dev`` to get another terminal: +Test the Go application by using ``curl`` to send a request to the root +endpoint. You will need a new terminal for this; use +``multipass shell charm-dev`` to open a new terminal in Multipass: .. code-block:: bash curl localhost:8080 -The Go application should respond with ``Hello, world!``. The Go application looks good, -so we can stop for now using :kbd:`Ctrl` + :kbd:`C`. +The Go application should respond with ``Hello, world!``. + +The Go application looks good, so we can stop it for now from the +original terminal using :kbd:`Ctrl` + :kbd:`C`. Pack the Go application into a rock ----------------------------------- -First, we'll need a ``rockcraft.yaml`` file. Rockcraft will automate its creation and -tailoring for a Go application using the ``go-framework`` profile. +First, we'll need a ``rockcraft.yaml`` file. Using the +``go-framework`` profile, Rockcraft will automate the creation of +``rockcraft.yaml`` and tailor the file for a Go application. +From the ``go-hello-world`` directory, initialize the rock: .. code-block:: bash rockcraft init --profile go-framework -The ``rockcraft.yaml`` file will be created automatically, with its name being set based -on your working directory. Open the file in a text editor and check that the ``name`` is -``go-hello-world``. Ensure that ``platforms`` includes the architecture of your host. -For example, if your host uses the ARM architecture, include ``arm64`` in ``platforms``. +The ``rockcraft.yaml`` file will automatically be created and set the name +based on your working directory. -.. note:: +Check out the contents of ``rockcraft.yaml``: - For this tutorial, we'll use the name ``go-hello-world`` and assume you are on the - ``amd64`` platform. Check the architecture of your system using ``dpkg - --print-architecture``. Choosing a different name or running a different platform - will influence the names of the files generated by Rockcraft. +.. code:: bash -Pack the rock: + cat rockcraft.yaml + +The top of the file should look similar to the following snippet: + +.. code:: yaml + + name: go-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: bare # as an alternative, a ubuntu base can be used + build-base: ubuntu@24.04 # build-base is required when the base is bare + version: '0.1' # just for humans. Semantic versioning is recommended + summary: A summary of your Go application # 79 char long summary + description: | + This is go-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Verfiy that the ``name`` is ``go-hello-world``. + +Ensure that ``platforms`` includes the architecture of your host. Check +the architecture of your system: .. code-block:: bash - ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + dpkg --print-architecture -.. note:: - Depending on your system and network, this step can take a couple of minutes to - finish. +If your host uses the ARM architecture, open ``rockcraft.yaml`` in a +text editor and include ``arm64`` in ``platforms``. -Once Rockcraft has finished packing the Go rock, you'll find a new file in your working -directory with the ``.rock`` extension. View its contents: +Now let's pack the rock: .. code-block:: bash - ls *.rock -l + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + +.. note:: + + ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required while the Go + extension is experimental. + +Depending on your system and network, this step can take several +minutes to finish. + +Once Rockcraft has finished packing the Go rock, +the terminal will respond with something similar to +``Packed go-hello-world_0.1_amd64.rock``. .. note:: - If you changed the ``name`` or ``version`` in ``rockcraft.yaml`` or are not on the - ``amd64`` platform, the name of the ``.rock`` file will be different for you. + If you are not on an ``amd64`` platform, the name of the ``.rock`` file + will be different for you. -The rock needs to be copied to the Microk8s registry so that it can be deployed in the -Kubernetes cluster: +The rock needs to be copied to the MicroK8s registry, which stores OCI +archives so they can be downloaded and deployed in the Kubernetes cluster. +Copy the rock: .. code-block:: bash @@ -244,19 +217,29 @@ Kubernetes cluster: oci-archive:go-hello-world_0.1_amd64.rock \ docker://localhost:32000/go-hello-world:0.1 +.. seealso:: + + See more: `Ubuntu manpage | skopeo + `_ Create the charm ---------------- -Create a new directory for the charm and go inside it: +From the ``go-hello-world`` directory, let's create a new directory +for the charm and change inside it: .. code-block:: bash - mkdir charm cd charm + mkdir charm + cd charm + +Using the ``go-framework`` profile, Charmcraft will automate the +creation of the files needed for our charm, including a +``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. +The source code contains the logic required to operate the Go +application. -We'll need a ``charmcraft.yaml``, ``requirements.txt`` and source code for the charm. -The source code contains the logic required to operate the Go application. Charmcraft -will automate the creation of these files by using the ``go-framework`` profile: +Initialize a charm named ``go-hello-world``: .. code-block:: bash @@ -273,20 +256,20 @@ The charm depends on several libraries. Download the libraries and pack the char .. note:: - Depending on your system and network, this step can take a couple of minutes to - finish. + ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required while the FastAPI + extension is experimental. -Once Charmcraft has finished packing the charm, you'll find a new file in your working -directory with the ``.charm`` extension. View its contents: - -.. code-block:: bash +Depending on your system and network, this step can take several +minutes to finish. - ls *.charm -l +Once Charmcraft has finished packing the charm, the terminal will +respond with something similar to +``Packed go-hello-world_ubuntu-24.04-amd64.charm``. .. note:: - If you changed the name in ``charmcraft.yaml`` or are not on the ``amd64`` platform, - the name of the ``.charm`` file will be different for you. + If you are not on the ``amd64`` platform, the name of the ``.charm`` + file will be different for you. Deploy the Go application From 486b1463607e3753177dd3ab50fd1be690e08b5e Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 13:09:29 -0500 Subject: [PATCH 41/51] docs: update go tutorial --- ...first-kubernetes-charm-for-a-flask-app.rst | 8 +- ...ur-first-kubernetes-charm-for-a-go-app.rst | 329 ++++++++++++------ 2 files changed, 224 insertions(+), 113 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst index 02c7e6a5b..58587dddb 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-flask-app.rst @@ -467,11 +467,11 @@ Now let's keep track of how many visitors your application has received. This will require integration with a database to keep the visitor count. This will require a few changes: -* We will need to create a database migration that creates the ``visitors`` table +* We will need to create a database migration that creates the ``visitors`` table. * We will need to keep track how many times the root endpoint has been called - in the database + in the database. * We will need to add a new endpoint to retrieve the number of visitors from the - database + database. Let's start with the database migration to create the required tables. The charm created by the ``flask-framework`` extension will execute the @@ -541,7 +541,7 @@ Change back into the charm directory using ``cd charm``. The Flask app now requires a database which needs to be declared in the ``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in a text editor and -add the following section to the end: +add the following section to the end of the file: .. literalinclude:: code/flask/visitors_charmcraft.yaml :language: yaml diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst index d0526da84..0283a27b6 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst @@ -275,35 +275,52 @@ respond with something similar to Deploy the Go application ------------------------- -A Juju model is needed to deploy the application. Let's create a enw model: +A Juju model is needed to handle Kubernetes resources while deploying +the Go application. Let's create a new model: .. code-block:: bash juju add-model go-hello-world -.. note:: +If you are not on a host with the ``amd64`` architecture, you will need to include +to include a constraint to the Juju model to specify your architecture. +You can check the architecture of your system using +``dpkg --print-architecture``. + +Set the Juju model constraints using - If you are not on a host with the ``amd64`` architecture, you will need to include a - constraint to the Juju model to specify your architecture. For example, using the - ``arm64`` architecture, you would use ``juju set-model-constraints -m - django-hello-world arch=arm64``. Check the architecture of your system using ``dpkg - --print-architecture``. +.. code-block:: bash -Now the Go application can be deployed using Juju: + juju set-model-constraints -m fastapi-hello-world \ + arch=$(dpkg --print-architecture) + +Now let’s use the OCI image we previously uploaded to deploy the Go +application. Deploy using Juju by specifying the OCI image name with the +``--resource`` option: .. code-block:: bash juju deploy ./go-hello-world_amd64.charm \ go-hello-world \ --resource app-image=localhost:32000/go-hello-world:0.1 -.. note:: +It will take a few minutes to deploy the Go application. You can monitor the +progress using - It will take a few minutes to deploy the FastAPI application. You can monitor the - progress using ``juju status --watch 5s``. Once the status of the app changes to - ``active``, you can stop watching using :kbd:`Ctrl` + :kbd:`C`. +.. code:: bash + + juju status --watch 2s + +It can take a couple of minutes for the app to finish the deployment. +Once the status of the App has gone to ``active``, you can stop watching +using :kbd:`Ctrl` + :kbd:`C`. + +.. seealso:: -The Go application should now be running. We can monitor the status of the deployment -using ``juju status``, which should be similar to the following output: + See more: :external+juju:ref:`Juju | juju status ` + +The Go application should now be running. We can monitor the status of +the deployment using ``juju status``, which should be similar to the +following output: .. terminal:: :input: juju status @@ -317,17 +334,16 @@ using ``juju status``, which should be similar to the following output: Unit Workload Agent Address Ports Message go-hello-world/0* active idle 10.1.157.79 -The deployment is finished when the status shows ``active``. Let's expose the -application using ingress. Deploy the ``nginx-ingress-integrator`` charm and integrate -it with the Go app: +Let's expose the application using ingress. Deploy the +``nginx-ingress-integrator`` charm and integrate it with the Go app: .. code-block:: bash - juju deploy nginx-ingress-integrator --trust juju integrate nginx-ingress-integrator - go-hello-world + juju deploy nginx-ingress-integrator --trust + juju integrate nginx-ingress-integrator go-hello-world -The hostname of the app needs to be defined so that it is accessible via the ingress. We -will also set the default route to be the root endpoint: +The hostname of the app needs to be defined so that it is accessible via +the ingress. We will also set the default route to be the root endpoint: .. code-block:: bash @@ -340,18 +356,26 @@ will also set the default route to be the root endpoint: the default port, it can be done with the configuration option ``app-port`` that will be exposed as the ``APP_PORT`` to the Go application. -Monitor ``juju status`` until everything has a status of ``active``. Use ``curl -http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` to send a request via the -ingress. The Go application should respond with ``Hello, world~``. +Monitor ``juju status`` until everything has a status of ``active``. + +Use ``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` +to send a request via the ingress. It should return the +``Hello, world~`` greeting. +.. note:: + + The ``--resolve go-hello-world:80:127.0.0.1`` option to the ``curl`` + command is a way of resolving the hostname of the request without + setting a DNS record. Configure the Go application ---------------------------- -Now let's customise the greeting using a configuration option. We will expect this -configuration option to be available in the Go app configuration under the keyword -``GREETING``. Go back out to the root directory of the project using ``cd ..`` and copy -the following code into ``main.go``: +To demonstrate how to provide a configuration to the Go application, +we will make the greeting configurable. We will expect this +configuration option to be available in the Go app configuration under the +keyword ``GREETING``. Change back to the ``go-hello-world`` directory using +``cd ..`` and copy the following code into ``main.go``: .. code-block:: c @@ -373,19 +397,48 @@ the following code into ``main.go``: helloWorldHandler) http.ListenAndServe(":8080", nil) } -Open ``rockcraft.yaml`` and update the version to ``0.2``. Run -``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack`` again, then upload the -new OCI image to the MicroK8s registry. +Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code:: yaml + :emphasize-lines: 6 + + name: go-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: bare # as an alternative, a ubuntu base can be used + build-base: ubuntu@24.04 # build-base is required when the base is bare + version: '0.2' # just for humans. Semantic versioning is recommended + summary: A summary of your Go application # 79 char long summary + description: | + This is go-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +Let's run the pack and upload commands for the rock: .. code-block:: bash + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:go-hello-world_0.2_amd64.rock \ docker://localhost:32000/go-hello-world:0.2 -Change back into the charm directory using ``cd charm``. The ``go-framework`` Charmcraft -extension supports adding configurations to ``charmcraft.yaml``, which will be passed as -environment variables to the Go application. Add the following to the end of the +Change back into the charm directory using ``cd charm``. + +The ``go-framework`` Charmcraft extension supports adding configurations +to ``charmcraft.yaml``, which will be passed as environment variables to +the Go application. Add the following to the end of the ``charmcraft.yaml`` file: .. code-block:: yaml @@ -399,21 +452,23 @@ environment variables to the Go application. Add the following to the end of the .. note:: - Configuration options are automatically capitalised and dashes are replaced by - underscores. An ``APP_`` prefix will also be added to ensure that environment - variables are namespaced. + Configuration options are automatically capitalized and ``-`` are replaced + by ``_``. An ``APP_`` prefix will also be added as a namespace + for app configurations. -Run ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack`` again. The -deployment can now be refreshed to make use of the new code: +We can now pack and deploy the new version of the Go app: .. code-block:: bash + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack juju refresh go-hello-world \ --path=./go-hello-world_amd64.charm \ --resource app-image=localhost:32000/go-hello-world:0.2 -Wait for ``juju status`` to show that the App is ``active`` again. Verify that the new -configuration has been added using ``juju config go-hello-world | grep -A 6 greeting:``, +After we wait for a bit monitoring ``juju status`` the application +should go back to ``active`` again. Verify that the new configuration +has been added using +``juju config go-hello-world | grep -A 6 greeting:``, which should show the configuration option. .. note:: @@ -421,42 +476,41 @@ which should show the configuration option. The ``grep`` command extracts a portion of the configuration to make it easier to check whether the configuration option has been added. -Using ``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` shows that -the response is still ``Hello, world!`` as expected. The greeting can be changed using -Juju: +Using ``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` +shows that the response is still ``Hello, world!`` as expected. + +Now let's change the greeting: .. code-block:: bash juju config go-hello-world greeting='Hi!' -``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` now returns the -updated ``Hi!`` greeting. - -.. note:: - - It might take a short time for the configuration to take effect. - +After we wait for a moment for the app to be restarted, using +``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` +should now return the updated ``Hi!`` greeting. Integrate with a database ------------------------- -Now let's keep track of how many visitors your application has received. This will -require integration with a database to keep the visitor count. This will require a few -changes: +Now let's keep track of how many visitors your application has received. +This will require integration with a database to keep the visitor count. +This will require a few changes: - We will need to create a database migration that creates the ``visitors`` table. -- We will need to keep track how many times the root endpoint has been called in the - database. +- We will need to keep track how many times the root endpoint has been called + in the database. - We will need to add a new endpoint to retrieve the number of visitors from the - database. -The charm created by the ``go-framework`` extension will execute the ``migrate.sh`` -script if it exists. This script should ensure that the database is initialised and -ready to be used by the application. We will create a ``migrate.sh`` file containing the -logic. +Let's start with the database migration to create the required tables. +The charm created by the ``go-framework`` extension will execute the +``migrate.sh`` script if it exists. This script should ensure that the +database is initialized and ready to be used by the application. We will +create a ``migrate.sh`` file containing this logic. -Go back out to the tutorial root directory using ``cd ..``. Create the ``migrate.sh`` -file using a text editor and paste the following code into it: +Go back out to the ``go-hello-world``directory using ``cd ..``. +Create the ``migrate.sh`` file using a text editor and paste the +following code into it: .. code-block:: bash @@ -469,8 +523,8 @@ file using a text editor and paste the following code into it: .. note:: The charm will pass the Database connection string in the - ``POSTGRESQL_DB_CONNECT_STRING`` environment variable once PostgreSQL has been - integrated with the charm. + ``POSTGRESQL_DB_CONNECT_STRING`` environment variable once + PostgreSQL has been integrated with the charm. Change the permissions of the file ``migrate.sh`` so that it is executable: @@ -478,11 +532,12 @@ Change the permissions of the file ``migrate.sh`` so that it is executable: chmod u+x migrate.sh -For the migrations to work, we need the ``postgresql-client`` package installed in the -rock. By default, the ``go-framework`` uses the ``base`` base, so we will also need to -install a shell interpreter. Let's do it as a slice, so that the rock does not include -unnecessary files. Open the ``rockcraft.yaml`` file using a text editor, update the -version to ``0.3`` and add the following to the end of the file: +For the migrations to work, we need the ``postgresql-client`` package +installed in the rock. By default, the ``go-framework`` uses the ``base`` +base, so we will also need to install a shell interpreter. Let's do it as a +slice, so that the rock does not include unnecessary files. Open the +``rockcraft.yaml`` file using a text editor and add the following to the +end of the file: .. code-block:: yaml @@ -494,9 +549,38 @@ version to ``0.3`` and add the following to the end of the file: plugin: nil stage-packages: - bash_bins -To be able to connect to PostgreSQL from the Go app, the library ``pgx`` will be used. -The app code needs to be updated to keep track of the number of visitors and to include -a new endpoint to retrieve the number of visitors. Open ``main.go`` in a text editor and +Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the +top of the ``rockcraft.yaml`` file looks similar to the following: + +.. code:: yaml + :emphasize-lines: 6 + + name: go-hello-world + # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ + # for more information about bases and using 'bare' bases for chiselled rocks + base: bare # as an alternative, a ubuntu base can be used + build-base: ubuntu@24.04 # build-base is required when the base is bare + version: '0.3' # just for humans. Semantic versioning is recommended + summary: A summary of your Go application # 79 char long summary + description: | + This is go-hello-world's description. You have a paragraph or two to tell the + most important story about it. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the + container registries out there. + # the platforms this rock should be built on and run on. + # you can check your architecture with `dpkg --print-architecture` + platforms: + amd64: + # arm64: + # ppc64el: + # s390x: + + ... + +To be able to connect to PostgreSQL from the Go app, the library +``pgx`` will be used. The app code needs to be updated to keep track of +the number of visitors and to include a new endpoint to retrieve the +number of visitors. Open ``main.go`` in a text editor and replace its content with the following code: .. dropdown:: main.go @@ -553,25 +637,27 @@ replace its content with the following code: http.ListenAndServe(":8080", nil) } -Check all the packages and their dependencies in the Go project with the following -command: +Check all the packages and their dependencies in the Go project with the +following command: .. code-block:: bash go mod tidy -Run ``ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack`` and upload the -newly created rock to the MicroK8s registry: +Let's run the pack and upload commands for the rock: .. code-block:: bash + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ oci-archive:go-hello-world_0.3_amd64.rock \ docker://localhost:32000/go-hello-world:0.3 -Go back into the charm directory using ``cd charm``. The Go app now requires a database -which needs to be declared in the ``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in -a text editor and add the following section to the end of the file: +Change back into the charm directory using ``cd charm``. + +The Go app now requires a database which needs to be declared in the +``charmcraft.yaml`` file. Open ``charmcraft.yaml`` in a text editor and +add the following section to the end of the file: .. code-block:: yaml @@ -579,45 +665,58 @@ a text editor and add the following section to the end of the file: postgresql: interface: postgresql_client optional: false -Pack the charm using ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack`` -and refresh the deployment using Juju: +We can now pack and deploy the new version of the Go app: .. code-block:: bash + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack juju refresh go-hello-world \ --path=./go-hello-world_amd64.charm \ --resource app-image=localhost:32000/go-hello-world:0.3 -Deploy ``postgresql-k8s`` using Juju and integrate it with ``go-hello-world``: +Now let’s deploy PostgreSQL and integrate it with the Go application: .. code-block:: bash - juju deploy postgresql-k8s --trust juju integrate go-hello-world postgresql-k8s + juju deploy postgresql-k8s --trust + juju integrate go-hello-world postgresql-k8s -Wait for ``juju status`` to show that the App is ``active`` again. Executing ``curl -http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` should still return the -``Hi!`` greeting. +Wait for ``juju status`` to show that the App is ``active`` again. +Running ``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` +should still return the ``Hi!`` greeting. -To check the local visitors, use ``curl http://go-hello-world/visitors --resolve -go-hello-world:80:127.0.0.1``, which should return ``Number of visitors 1`` after the -previous request to the root endpoint. This should be incremented each time the root -endpoint is requested. If we repeat this process, the output should be as follows: +To check the local visitors, use +``curl http://go-hello-world/visitors --resolve go-hello-world:80:127.0.0.1``, +which should return ``Number of visitors 1`` after the +previous request to the root endpoint. +This should be incremented each time the root endpoint is requested. If we +repeat this process, the output should be as follows: .. terminal:: :input: curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1 - Hi! :input: curl http://go-hello-world/visitors --resolve - go-hello-world:80:127.0.0.1 Number of visitors 2 + Hi! + :input: curl http://go-hello-world/visitors --resolve go-hello-world:80:127.0.0.1 + Number of visitors 2 Tear things down ---------------- -We've reached the end of this tutorial. We have created a Go application, deployed it -locally, integrated it with a database and exposed it via ingress! +We’ve reached the end of this tutorial. We went through the entire +development process, including: -If you'd like to reset your working environment, you can run the following in the root -directory for the tutorial: +- Creating a Go application +- Deploying the application locally +- Building an OCI image using Rockcraft +- Packaging the application using Charmcraft +- Deplyoing the application using Juju +- Exposing the application using an ingress +- Configuring the application +- Integrating the application with a database + +If you'd like to reset your working environment, you can run the following +in the rock directory ``go-hello-world`` for the tutorial: .. code-block:: bash @@ -627,14 +726,13 @@ directory for the tutorial: go.mod go.sum # Remove the juju model juju destroy-model go-hello-world --destroy-storage -If you created an instance using Multipass, you can also clean it up. Start by exiting -it: +You can also clean up your Multipass instance. Start by exiting it: .. code-block:: bash exit -You can then proceed with its deletion: +And then you can proceed with its deletion: .. code-block:: bash @@ -644,13 +742,26 @@ You can then proceed with its deletion: Next steps ---------- -By the end of this tutorial, you will have built a charm and evolved it in a number of -practical ways, but there is a lot more to explore: - -+-------------------------+----------------------+ -| If you are wondering... | Visit... | -+=========================+======================+ -| "How do I...?" | :ref:`how-to-guides` | -+-------------------------+----------------------+ -| "What is...?" | :ref:`reference` | -+-------------------------+----------------------+ +By the end of this tutorial you will have built a charm and evolved it +in a number of typical ways. But there is a lot more to explore: + +.. list-table:: + :widths: 30 30 + :header-rows: 1 + + * - If you are wondering... + - Visit... + * - "How do I...?" + - :ref:`How-to guides `, + :external+ops:ref:`Ops | How-to guides ` + * - "How do I debug?" + - `Charm debugging tools `_ + * - "How do I get in touch?" + - `Matrix channel `_ + * - "What is...?" + - :ref:`reference`, + :external+ops:ref:`Ops | Reference `, + :external+juju:ref:`Juju | Reference ` + * - "Why...?", "So what?" + - :external+ops:ref:`Ops | Explanation `, + :external+juju:ref:`Juju | Explanation ` From 41ef9df9d2e895e78bce248fce780625ed1922cf Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 13:11:42 -0500 Subject: [PATCH 42/51] fix: make lint happy --- .../write-your-first-kubernetes-charm-for-a-go-app.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst index 0283a27b6..3e202aa6d 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst @@ -508,7 +508,7 @@ The charm created by the ``go-framework`` extension will execute the database is initialized and ready to be used by the application. We will create a ``migrate.sh`` file containing this logic. -Go back out to the ``go-hello-world``directory using ``cd ..``. +Go back out to the ``go-hello-world`` directory using ``cd ..``. Create the ``migrate.sh`` file using a text editor and paste the following code into it: @@ -696,7 +696,7 @@ repeat this process, the output should be as follows: :input: curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1 Hi! - :input: curl http://go-hello-world/visitors --resolve go-hello-world:80:127.0.0.1 + :input: curl http://go-hello-world/visitors --resolve go-hello-world:80:127.0.0.1 Number of visitors 2 From 968734cb6be849bbf5cf61325e12876abba7129d Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 13:17:12 -0500 Subject: [PATCH 43/51] fix: update reusable setups --- docs/reuse/tutorial/setup_edge.rst | 6 +++--- docs/reuse/tutorial/setup_stable.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reuse/tutorial/setup_edge.rst b/docs/reuse/tutorial/setup_edge.rst index 66a9bc928..984495fb2 100644 --- a/docs/reuse/tutorial/setup_edge.rst +++ b/docs/reuse/tutorial/setup_edge.rst @@ -27,7 +27,7 @@ In order to create the rock, you'll need to install Rockcraft with the ``LXD`` will be required for building the rock. -Make sure it is installed and initialised: +Make sure it is installed and initialized: .. code-block:: bash @@ -69,9 +69,9 @@ Several MicroK8s add-ons are required for deployment: # Required for Juju to provide storage volumes sudo microk8s enable hostpath-storage - # Required to host the OCI image of the 12-Factor application + # Required to host the OCI image of the application sudo microk8s enable registry - # Required to expose the 12-Factor application + # Required to expose the application sudo microk8s enable ingress Juju is required to deploy the |12FactorApp| application. diff --git a/docs/reuse/tutorial/setup_stable.rst b/docs/reuse/tutorial/setup_stable.rst index 8aaacfd37..f24549c88 100644 --- a/docs/reuse/tutorial/setup_stable.rst +++ b/docs/reuse/tutorial/setup_stable.rst @@ -26,7 +26,7 @@ In order to create the rock, you'll need to install Rockcraft with the sudo snap install rockcraft --classic ``LXD`` will be required for building the rock. -Make sure it is installed and initialised: +Make sure it is installed and initialized: .. code-block:: bash @@ -70,9 +70,9 @@ Several MicroK8s add-ons are required for deployment: # Required for Juju to provide storage volumes sudo microk8s enable hostpath-storage - # Required to host the OCI image of the 12-Factor application + # Required to host the OCI image of the application sudo microk8s enable registry - # Required to expose the 12-Factor application + # Required to expose the application sudo microk8s enable ingress Juju is required to deploy the |12FactorApp| application. From d66f8b36363e7c306fb4a678ee11f117ae52808d Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 13:32:22 -0500 Subject: [PATCH 44/51] fix: correct issues with go tutorial --- ...ur-first-kubernetes-charm-for-a-go-app.rst | 113 +++++++++++------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst index 3e202aa6d..7e5487eb8 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst @@ -51,7 +51,7 @@ Set things up ------------- .. include:: /reuse/tutorial/setup_edge.rst -.. |12FactorApp| replace: Go +.. |12FactorApp| replace:: Go Finally, let's create a new directory for this tutorial and change into it: @@ -71,7 +71,8 @@ Install ``go`` and initialize the Go module: .. code-block:: bash - sudo snap install go --classic go mod init go-hello-world + sudo snap install go --classic + go mod init go-hello-world Create a ``main.go`` file, copy the following text into it and then save it: @@ -81,16 +82,20 @@ save it: package main import ( - "fmt" "log" "net/http" + "fmt" + "log" + "net/http" ) func helloWorldHandler(w http.ResponseWriter, req *http.Request) { - log.Printf("new hello world request") fmt.Fprintln(w, "Hello, world!") + log.Printf("new hello world request") + fmt.Fprintln(w, "Hello, world!") } func main() { - log.Printf("starting hello world application") http.HandleFunc("/", - helloWorldHandler) http.ListenAndServe(":8080", nil) + log.Printf("starting hello world application") + http.HandleFunc("/", helloWorldHandler) + http.ListenAndServe(":8080", nil) } @@ -147,7 +152,7 @@ Check out the contents of ``rockcraft.yaml``: The top of the file should look similar to the following snippet: -.. code:: yaml +.. code-block:: yaml name: go-hello-world # see https://documentation.ubuntu.com/rockcraft/en/latest/explanation/bases/ @@ -256,7 +261,7 @@ The charm depends on several libraries. Download the libraries and pack the char .. note:: - ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required while the FastAPI + ``CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS`` is required while the Go extension is experimental. Depending on your system and network, this step can take several @@ -291,7 +296,7 @@ Set the Juju model constraints using .. code-block:: bash - juju set-model-constraints -m fastapi-hello-world \ + juju set-model-constraints -m go-hello-world \ arch=$(dpkg --print-architecture) Now let’s use the OCI image we previously uploaded to deploy the Go @@ -325,6 +330,7 @@ following output: .. terminal:: :input: juju status + Model Controller Cloud/Region Version SLA Timestamp go-hello-world microk8s microk8s/localhost 3.5.4 unsupported 14:35:07+02:00 App Version Status Scale Charm Channel Rev Address @@ -360,7 +366,7 @@ Monitor ``juju status`` until everything has a status of ``active``. Use ``curl http://go-hello-world --resolve go-hello-world:80:127.0.0.1`` to send a request via the ingress. It should return the -``Hello, world~`` greeting. +``Hello, world!`` greeting. .. note:: @@ -382,25 +388,31 @@ keyword ``GREETING``. Change back to the ``go-hello-world`` directory using package main import ( - "fmt" "log" "os" "net/http" + "fmt" + "log" + "os" + "net/http" ) func helloWorldHandler(w http.ResponseWriter, req *http.Request) { - log.Printf("new hello world request") greeting, found := - os.LookupEnv("APP_GREETING") if !found { - greeting = "Hello, world!" - } fmt.Fprintln(w, greeting) + log.Printf("new hello world request") + greeting, found := os.LookupEnv("APP_GREETING") + if !found { + greeting = "Hello, world!" + } + fmt.Fprintln(w, greeting) } func main() { - log.Printf("starting hello world application") http.HandleFunc("/", - helloWorldHandler) http.ListenAndServe(":8080", nil) + log.Printf("starting hello world application") + http.HandleFunc("/", helloWorldHandler) + http.ListenAndServe(":8080", nil) } Increment the ``version`` in ``rockcraft.yaml`` to ``0.2`` such that the top of the ``rockcraft.yaml`` file looks similar to the following: -.. code:: yaml +.. code-block:: yaml :emphasize-lines: 6 name: go-hello-world @@ -499,8 +511,8 @@ This will require a few changes: - We will need to create a database migration that creates the ``visitors`` table. - We will need to keep track how many times the root endpoint has been called in the database. -- We will need to add a new endpoint to retrieve the number of visitors from the -- database. +- We will need to add a new endpoint to retrieve the number of visitors from + the database. Let's start with the database migration to create the required tables. The charm created by the ``go-framework`` extension will execute the @@ -516,9 +528,7 @@ following code into it: #!/bin/bash - PGPASSWORD="${POSTGRESQL_DB_PASSWORD}" psql -h "${POSTGRESQL_DB_HOSTNAME}" -U - "${POSTGRESQL_DB_USERNAME}" "${POSTGRESQL_DB_NAME}" -c "CREATE TABLE IF NOT EXISTS - visitors (timestamp TIMESTAMP NOT NULL, user_agent TEXT NOT NULL);" + PGPASSWORD="${POSTGRESQL_DB_PASSWORD}" psql -h "${POSTGRESQL_DB_HOSTNAME}" -U "${POSTGRESQL_DB_USERNAME}" "${POSTGRESQL_DB_NAME}" -c "CREATE TABLE IF NOT EXISTS visitors (timestamp TIMESTAMP NOT NULL, user_agent TEXT NOT NULL);" .. note:: @@ -552,7 +562,7 @@ end of the file: Increment the ``version`` in ``rockcraft.yaml`` to ``0.3`` such that the top of the ``rockcraft.yaml`` file looks similar to the following: -.. code:: yaml +.. code-block:: yaml :emphasize-lines: 6 name: go-hello-world @@ -590,27 +600,35 @@ replace its content with the following code: package main import ( - "database/sql" "fmt" "log" "net/http" "os" "time" + "database/sql" + "fmt" + "log" + "net/http" + "os" + "time" _ "github.com/jackc/pgx/v5/stdlib" ) func helloWorldHandler(w http.ResponseWriter, req *http.Request) { - log.Printf("new hello world request") postgresqlURL := - os.Getenv("POSTGRESQL_DB_CONNECT_STRING") db, err := sql.Open("pgx", - postgresqlURL) if err != nil { - log.Printf("An error occurred while connecting to postgresql: - %v", err) return + log.Printf("new hello world request") + postgresqlURL := os.Getenv("POSTGRESQL_DB_CONNECT_STRING") + db, err := sql.Open("pgx", postgresqlURL) + if err != nil { + log.Printf("An error occurred while connecting to postgresql: %v", err) + return } defer db.Close() - ua := req.Header.Get("User-Agent") timestamp := time.Now() _, err = - db.Exec("INSERT into visitors (timestamp, user_agent) VALUES ($1, $2)", - timestamp, ua) if err != nil { + ua := req.Header.Get("User-Agent") + timestamp := time.Now() _, + err = db.Exec("INSERT into visitors (timestamp, user_agent) VALUES ($1, $2)", timestamp, ua) + if err != nil { log.Printf("An error occurred while executing query: %v", err) return } - greeting, found := os.LookupEnv("APP_GREETING") if !found { + greeting, found := os.LookupEnv("APP_GREETING") + if !found { greeting = "Hello, world!" } @@ -618,22 +636,27 @@ replace its content with the following code: } func visitorsHandler(w http.ResponseWriter, req *http.Request) { - log.Printf("visitors request") postgresqlURL := - os.Getenv("POSTGRESQL_DB_CONNECT_STRING") db, err := sql.Open("pgx", - postgresqlURL) if err != nil { + log.Printf("visitors request") + postgresqlURL := os.Getenv("POSTGRESQL_DB_CONNECT_STRING") + db, err := sql.Open("pgx", postgresqlURL) + if err != nil { return - } defer db.Close() + } + defer db.Close() - var numVisitors int err = db.QueryRow("SELECT count(*) from - visitors").Scan(&numVisitors) if err != nil { + var numVisitors + int err = db.QueryRow("SELECT count(*) from visitors").Scan(&numVisitors) + if err != nil { log.Printf("An error occurred while executing query: %v", err) return - } fmt.Fprintf(w, "Number of visitors %d\n", numVisitors) + } + fmt.Fprintf(w, "Number of visitors %d\n", numVisitors) } func main() { - log.Printf("starting hello world application") http.HandleFunc("/", - helloWorldHandler) http.HandleFunc("/visitors", visitorsHandler) + log.Printf("starting hello world application") + http.HandleFunc("/", helloWorldHandler) + http.HandleFunc("/visitors", visitorsHandler) http.ListenAndServe(":8080", nil) } @@ -671,8 +694,8 @@ We can now pack and deploy the new version of the Go app: CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack juju refresh go-hello-world \ - --path=./go-hello-world_amd64.charm \ --resource - app-image=localhost:32000/go-hello-world:0.3 + --path=./go-hello-world_amd64.charm \ + --resource app-image=localhost:32000/go-hello-world:0.3 Now let’s deploy PostgreSQL and integrate it with the Go application: From 4864aa0e332efe70afb869ba682b8860dba5b885 Mon Sep 17 00:00:00 2001 From: erinecon Date: Fri, 31 Jan 2025 14:30:36 -0500 Subject: [PATCH 45/51] test: remove checks in flask spread --- docs/tutorial/code/flask/task.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index d23d1912d..8e2a5d9a0 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -16,12 +16,6 @@ execute: | mv *.yaml *.py *.txt $HOME cd $HOME - # Test/debug - microk8s version - juju version - charmcraft --version - rockcraft --version - # Don't use the staging store for this test unset CHARMCRAFT_STORE_API_URL unset CHARMCRAFT_UPLOAD_URL From be40d40e515caed7b06f4f7049797a78d96c752f Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 6 Feb 2025 08:33:50 -0500 Subject: [PATCH 46/51] fix: update flask task.yaml --- docs/tutorial/code/flask/task.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/tutorial/code/flask/task.yaml b/docs/tutorial/code/flask/task.yaml index 8e2a5d9a0..6b745e441 100644 --- a/docs/tutorial/code/flask/task.yaml +++ b/docs/tutorial/code/flask/task.yaml @@ -79,10 +79,6 @@ execute: | charmcraft pack # [docs:charm-pack-end] - # [docs:ls-charm] - ls *.charm -l - # [docs:ls-charm-end] - # [docs:add-juju-model] juju add-model flask-hello-world # [docs:add-juju-model-end] From 5c236183f2dea074c2536ccdb330591f64958b60 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 6 Feb 2025 08:37:21 -0500 Subject: [PATCH 47/51] fix: update curl command --- .../write-your-first-kubernetes-charm-for-a-fastapi-app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index c4162f29a..d6a5db4c1 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -363,7 +363,7 @@ the ingress. We will also set the default route to be the root endpoint: Monitor ``juju status`` until everything has a status of ``active``. Test the deployment using -``curl http://fastapi-hello-world --resolve fast-api-hello-world:80:127.0.0.1`` +``curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` to send a request via the ingress. It should return the ``{"message":"Hello World"}`` greeting. From 7cd1575e1afa811938c80aa1705492b82458b506 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 6 Feb 2025 08:42:33 -0500 Subject: [PATCH 48/51] fix: update curl command --- .../write-your-first-kubernetes-charm-for-a-fastapi-app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst index d6a5db4c1..135f64ec4 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app.rst @@ -474,7 +474,7 @@ the configuration option. The ``grep`` command extracts a portion of the configuration to make it easier to check whether the configuration option has been added. -Using ``http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` +Using ``curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1`` shows that the response is still ``{"message":"Hello, world!"}`` as expected. Now let's change the greeting: From c29053a4bc145774da08bd2fc52f17a931d15643 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 6 Feb 2025 14:17:27 -0500 Subject: [PATCH 49/51] fix: update greeting config snippet --- .../write-your-first-kubernetes-charm-for-a-go-app.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst index 7e5487eb8..351c2b18a 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst @@ -460,7 +460,8 @@ the Go application. Add the following to the end of the greeting: description: | The greeting to be returned by the Go application. - default: "Hello, world!" type: string + default: "Hello, world!" + type: string .. note:: From 89b52c8e7ec9f4e9d292e3502f8504a53f8daf1c Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 6 Feb 2025 14:25:17 -0500 Subject: [PATCH 50/51] fix: update visitors requires snippet --- .../write-your-first-kubernetes-charm-for-a-go-app.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst index 351c2b18a..ed2877ce4 100644 --- a/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst +++ b/docs/tutorial/write-your-first-kubernetes-charm-for-a-go-app.rst @@ -687,7 +687,8 @@ add the following section to the end of the file: requires: postgresql: - interface: postgresql_client optional: false + interface: postgresql_client + optional: false We can now pack and deploy the new version of the Go app: From b1ddb79ddebfe26a894ecd3b44f23154c0425470 Mon Sep 17 00:00:00 2001 From: erinecon Date: Thu, 6 Feb 2025 14:36:59 -0500 Subject: [PATCH 51/51] docs: add fastapi tutorial spread test --- docs/tutorial/code/fastapi/app.py | 7 + docs/tutorial/code/fastapi/greeting_app.py | 9 + .../code/fastapi/greeting_charmcraft.yaml | 9 + docs/tutorial/code/fastapi/requirements.txt | 2 + docs/tutorial/code/fastapi/task.yaml | 206 ++++++++++++++++++ docs/tutorial/code/fastapi/visitors_app.py | 34 +++ .../code/fastapi/visitors_charmcraft.yaml | 6 + .../tutorial/code/fastapi/visitors_migrate.py | 21 ++ 8 files changed, 294 insertions(+) create mode 100644 docs/tutorial/code/fastapi/app.py create mode 100644 docs/tutorial/code/fastapi/greeting_app.py create mode 100644 docs/tutorial/code/fastapi/greeting_charmcraft.yaml create mode 100644 docs/tutorial/code/fastapi/requirements.txt create mode 100644 docs/tutorial/code/fastapi/task.yaml create mode 100644 docs/tutorial/code/fastapi/visitors_app.py create mode 100644 docs/tutorial/code/fastapi/visitors_charmcraft.yaml create mode 100644 docs/tutorial/code/fastapi/visitors_migrate.py diff --git a/docs/tutorial/code/fastapi/app.py b/docs/tutorial/code/fastapi/app.py new file mode 100644 index 000000000..8e3dfea72 --- /dev/null +++ b/docs/tutorial/code/fastapi/app.py @@ -0,0 +1,7 @@ +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +async def root(): + return {"message": "Hello World"} \ No newline at end of file diff --git a/docs/tutorial/code/fastapi/greeting_app.py b/docs/tutorial/code/fastapi/greeting_app.py new file mode 100644 index 000000000..9183838d1 --- /dev/null +++ b/docs/tutorial/code/fastapi/greeting_app.py @@ -0,0 +1,9 @@ +import os + +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +async def root(): + return {"message": os.getenv("APP_GREETING", "Hello World")} \ No newline at end of file diff --git a/docs/tutorial/code/fastapi/greeting_charmcraft.yaml b/docs/tutorial/code/fastapi/greeting_charmcraft.yaml new file mode 100644 index 000000000..e737da7e0 --- /dev/null +++ b/docs/tutorial/code/fastapi/greeting_charmcraft.yaml @@ -0,0 +1,9 @@ +# configuration snippet for FastAPI application with a configuration + +config: + options: + greeting: + description: | + The greeting to be returned by the FastAPI application. + default: "Hello, world!" + type: string diff --git a/docs/tutorial/code/fastapi/requirements.txt b/docs/tutorial/code/fastapi/requirements.txt new file mode 100644 index 000000000..472fd3835 --- /dev/null +++ b/docs/tutorial/code/fastapi/requirements.txt @@ -0,0 +1,2 @@ +fastapi[standard] +psycopg2-binary diff --git a/docs/tutorial/code/fastapi/task.yaml b/docs/tutorial/code/fastapi/task.yaml new file mode 100644 index 000000000..d00d45731 --- /dev/null +++ b/docs/tutorial/code/fastapi/task.yaml @@ -0,0 +1,206 @@ +########################################### +# IMPORTANT +# Comments matter! +# The docs use the wrapping comments as +# markers for including said instructions +# as snippets in the docs. +########################################### +summary: Getting started with FastAPI tutorial + +kill-timeout: 180m + +environment: + +execute: | + # Move everything to $HOME so that Juju deployment works + mv *.yaml *.py *.txt $HOME + cd $HOME + + # Don't use the staging store for this test + unset CHARMCRAFT_STORE_API_URL + unset CHARMCRAFT_UPLOAD_URL + unset CHARMCRAFT_REGISTRY_URL + + # MicroK8s config setup + microk8s status --wait-ready + microk8s enable hostpath-storage + microk8s enable registry + microk8s enable ingress + + # Bootstrap controller + juju bootstrap microk8s dev-controller + + # [docs:create-venv] + sudo apt-get update && sudo apt-get install python3-venv -y + python3 -m venv .venv + source .venv/bin/activate + # [docs:create-venv-end] + + # [docs:install-requirements] + pip install -r requirements.txt + # [docs:install-requirements-end] + + fastapi dev app.py --port 8080 & + retry -n 5 --wait 2 curl --fail localhost:8080 + + # [docs:curl-fastapi] + curl localhost:8080 + # [docs:curl-fastapi-end] + + kill $! + + # [docs:create-rockcraft-yaml] + rockcraft init --profile fastapi-framework + # [docs:create-rockcraft-yaml-end] + + sed -i "s/name: .*/name: fastapi-hello-world/g" rockcraft.yaml + sed -i "s/amd64/$(dpkg --print-architecture)/g" rockcraft.yaml + + # [docs:pack] + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + # [docs:pack-end] + + # [docs:skopeo-copy] + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:fastapi-hello-world_0.1_$(dpkg --print-architecture).rock \ + docker://localhost:32000/fastapi-hello-world:0.1 + # [docs:skopeo-copy-end] + + # [docs:create-charm-dir] + mkdir charm + cd charm + # [docs:create-charm-dir-end] + + # [docs:charm-init] + charmcraft init --profile fastapi-framework --name fastapi-hello-world + # [docs:charm-init-end] + + # update platforms in charmcraft.yaml file + sed -i "s/amd64/$(dpkg --print-architecture)/g" charmcraft.yaml + + # [docs:charm-pack] + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + # [docs:charm-pack-end] + + # [docs:add-juju-model] + juju add-model fastapi-hello-world + # [docs:add-juju-model-end] + + #[docs:add-model-constraints] + juju set-model-constraints -m fastapi-hello-world arch=$(dpkg --print-architecture) + #[docs:add-model-constraints-end] + + # [docs:deploy-fastapi-app] + juju deploy \ + ./fastapi-hello-world_$(dpkg --print-architecture).charm \ + fastapi-hello-world --resource \ + app-image=localhost:32000/fastapi-hello-world:0.1 + # [docs:deploy-fastapi-app-end] + + # [docs:deploy-nginx] + juju deploy nginx-ingress-integrator --channel=latest/stable --trust + juju integrate nginx-ingress-integrator fastapi-hello-world + # [docs:deploy-nginx-end] + + # [docs:config-nginx] + juju config nginx-ingress-integrator \ + service-hostname=fastapi-hello-world path-routes=/ + # [docs:config-nginx-end] + + # give Juju some time to deploy the apps + juju wait-for application fastapi-hello-world --query='status=="active"' --timeout 10m + juju wait-for application nginx-ingress-integrator --query='status=="active"' --timeout 10m + + # [docs:curl-init-deployment] + curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1 + # [docs:curl-init-deployment-end] + + cd .. + cat greeting_app.py > app.py + sed -i "s/version: .*/version: 0.2/g" rockcraft.yaml + + # [docs:docker-update] + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:fastapi-hello-world_0.2_$(dpkg --print-architecture).rock \ + docker://localhost:32000/fastapi-hello-world:0.2 + # [docs:docker-update-end] + + cat greeting_charmcraft.yaml >> ./charm/charmcraft.yaml + cd charm + + # [docs:refresh-deployment] + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + juju refresh fastapi-hello-world \ + --path=./fastapi-hello-world_$(dpkg --print-architecture).charm \ + --resource app-image=localhost:32000/fastapi-hello-world:0.2 + # [docs:refresh-deployment-end] + + # give Juju some time to refresh the app + juju wait-for application fastapi-hello-world --query='status=="active"' --timeout 10m + + # curl and check that the response is Hello + curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1 | grep Hello + + # [docs:change-config] + juju config fastapi-hello-world greeting='Hi!' + # [docs:change-config-end] + + # make sure that the application updates + juju wait-for application fastapi-hello-world --query='status=="maintenance"' --timeout 10m + juju wait-for application fastapi-hello-world --query='status=="active"' --timeout 10m + + # curl and check that the response is now Hi + curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1 | grep Hi + + cd .. + cat visitors_migrate.py >> migrate.py + cat visitors_app.py > app.py + sed -i "s/version: .*/version: 0.3/g" rockcraft.yaml + + # [docs:docker-2nd-update] + ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true rockcraft pack + rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ + oci-archive:fastapi-hello-world_0.3_$(dpkg --print-architecture).rock \ + docker://localhost:32000/fastapi-hello-world:0.3 + # [docs:docker-2nd-update-end] + + cat visitors_charmcraft.yaml >> ./charm/charmcraft.yaml + cd charm + + # [docs:refresh-2nd-deployment] + CHARMCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true charmcraft pack + juju refresh fastapi-hello-world \ + --path=./fastapi-hello-world_$(dpkg --print-architecture).charm \ + --resource app-image=localhost:32000/fastapi-hello-world:0.3 + # [docs:refresh-2nd-deployment-end] + + # [docs:deploy-postgres] + juju deploy postgresql-k8s --trust + juju integrate fastapi-hello-world postgresql-k8s + # [docs:deploy-postgres-end] + + # give Juju some time to deploy and refresh the apps + juju wait-for application postgresql-k8s --query='status=="active"' --timeout 20m | juju status --relations + juju wait-for application fastapi-hello-world --query='status=="active"' --timeout 20m | juju status --relations + + curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1 | grep Hi + curl http://fastapi-hello-world/visitors --resolve fastapi-hello-world:80:127.0.0.1 | grep 1 + curl http://fastapi-hello-world --resolve fastapi-hello-world:80:127.0.0.1 | grep Hi + curl http://fastapi-hello-world/visitors --resolve fastapi-hello-world:80:127.0.0.1 | grep 2 + + # Back out to main directory for cleanup + cd .. + + # [docs:clean-environment] + # exit and delete the virtual environment + deactivate + rm -rf charm .venv __pycache__ + # delete all the files created during the tutorial + rm fastapi-hello-world_0.1_$(dpkg --print-architecture).rock \ + fastapi-hello-world_0.2_$(dpkg --print-architecture).rock \ + fastapi-hello-world_0.3_$(dpkg --print-architecture).rock \ + rockcraft.yaml app.py requirements.txt migrate.py + # Remove the juju model + juju destroy-model fastapi-hello-world --destroy-storage --no-prompt --force + # [docs:clean-environment-end] diff --git a/docs/tutorial/code/fastapi/visitors_app.py b/docs/tutorial/code/fastapi/visitors_app.py new file mode 100644 index 000000000..d74e25bdc --- /dev/null +++ b/docs/tutorial/code/fastapi/visitors_app.py @@ -0,0 +1,34 @@ +# FastAPI application that keeps track of visitors using a database + +import datetime +import os +from typing import Annotated + +from fastapi import FastAPI, Header +import psycopg2 + +app = FastAPI() +DATABASE_URI = os.environ["POSTGRESQL_DB_CONNECT_STRING"] + + +@app.get("/") +async def root(user_agent: Annotated[str | None, Header()] = None): + with psycopg2.connect(DATABASE_URI) as conn, conn.cursor() as cur: + timestamp = datetime.datetime.now() + + cur.execute( + "INSERT INTO visitors (timestamp, user_agent) VALUES (%s, %s)", + (timestamp, user_agent) + ) + conn.commit() + + return {"message": os.getenv("APP_GREETING", "Hello World")} + + +@app.get("/visitors") +async def visitors(): + with psycopg2.connect(DATABASE_URI) as conn, conn.cursor() as cur: + cur.execute("SELECT COUNT(*) FROM visitors") + total_visitors = cur.fetchone()[0] + + return {"count": total_visitors} diff --git a/docs/tutorial/code/fastapi/visitors_charmcraft.yaml b/docs/tutorial/code/fastapi/visitors_charmcraft.yaml new file mode 100644 index 000000000..2cc0cb84e --- /dev/null +++ b/docs/tutorial/code/fastapi/visitors_charmcraft.yaml @@ -0,0 +1,6 @@ +# requires snippet for FastAPI application with a database + +requires: + postgresql: + interface: postgresql_client + optional: false diff --git a/docs/tutorial/code/fastapi/visitors_migrate.py b/docs/tutorial/code/fastapi/visitors_migrate.py new file mode 100644 index 000000000..9101e06b1 --- /dev/null +++ b/docs/tutorial/code/fastapi/visitors_migrate.py @@ -0,0 +1,21 @@ +# Adds database to FastAPI application + +import os + +import psycopg2 + +DATABASE_URI = os.environ["POSTGRESQL_DB_CONNECT_STRING"] + +def migrate(): + with psycopg2.connect(DATABASE_URI) as conn, conn.cursor() as cur: + cur.execute(""" + CREATE TABLE IF NOT EXISTS visitors ( + timestamp TIMESTAMP NOT NULL, + user_agent TEXT NOT NULL + ); + """) + conn.commit() + + +if __name__ == "__main__": + migrate()