feat: working example

This commit is contained in:
Tine 2024-03-01 22:05:49 +01:00
parent a99fdaf1f9
commit ac08d5a715
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
14 changed files with 263 additions and 17 deletions

View file

@ -3,7 +3,7 @@ FROM python:3.10-alpine
WORKDIR /app WORKDIR /app
COPY requirements.txt /app COPY src/requirements.txt /app
RUN pip3 install -r requirements.txt RUN pip3 install -r requirements.txt
COPY src /app COPY src /app

View file

@ -16,8 +16,12 @@ cp example.env .env
just run just run
# Provision infrastructure # Provision infrastructure
just terraform apply just deploy
# Deploy new code ## RELEASE PROCESS
# Release new code
just release
# At this point, you have to modify .env to point to new image
# And then we can deploy the new image.
just deploy just deploy
``` ```

View file

@ -5,7 +5,9 @@
"python312Packages.pip@latest", "python312Packages.pip@latest",
"terraform@latest", "terraform@latest",
"azure-cli@latest", "azure-cli@latest",
"azure-functions-core-tools@latest" "azure-functions-core-tools@latest",
"black@latest",
"postgresql@latest"
], ],
"env": { "env": {
"VENV_DIR": ".venv" "VENV_DIR": ".venv"

View file

@ -38,6 +38,26 @@
} }
} }
}, },
"black@latest": {
"last_modified": "2024-02-26T19:46:43Z",
"resolved": "github:NixOS/nixpkgs/548a86b335d7ecd8b57ec617781f5e652ab0c38e#black",
"source": "devbox-search",
"version": "23.11.0",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/4aks55v8kdck409q1ga0qkdnrphabv53-python3.11-black-23.11.0"
},
"aarch64-linux": {
"store_path": "/nix/store/y7kcifhk0v58pfhqdmpdbpd0yxwv860z-python3.11-black-23.11.0"
},
"x86_64-darwin": {
"store_path": "/nix/store/41jmbap8q6gsjs8g59xwr6f222pdzpp8-python3.11-black-23.11.0"
},
"x86_64-linux": {
"store_path": "/nix/store/g8k9vjxgsy8d3dizjw1jlcmj2iphy512-python3.11-black-23.11.0"
}
}
},
"just@latest": { "just@latest": {
"last_modified": "2024-02-26T19:46:43Z", "last_modified": "2024-02-26T19:46:43Z",
"resolved": "github:NixOS/nixpkgs/548a86b335d7ecd8b57ec617781f5e652ab0c38e#just", "resolved": "github:NixOS/nixpkgs/548a86b335d7ecd8b57ec617781f5e652ab0c38e#just",
@ -58,6 +78,27 @@
} }
} }
}, },
"postgresql@latest": {
"last_modified": "2024-02-26T19:46:43Z",
"plugin_version": "0.0.2",
"resolved": "github:NixOS/nixpkgs/548a86b335d7ecd8b57ec617781f5e652ab0c38e#postgresql",
"source": "devbox-search",
"version": "15.6",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/j5m8ndrxpmk1444lzdk6p33vfzwrg6l7-postgresql-15.6"
},
"aarch64-linux": {
"store_path": "/nix/store/3gbmk22frf8l07k3mhb16d43dy7bfd2s-postgresql-15.6"
},
"x86_64-darwin": {
"store_path": "/nix/store/3a6lihh8nk9fvhf87vwkszfknjx27yfb-postgresql-15.6"
},
"x86_64-linux": {
"store_path": "/nix/store/84xh621k32sc7ykqx37vcqdpjz9zxmdf-postgresql-15.6"
}
}
},
"python312Packages.pip@latest": { "python312Packages.pip@latest": {
"last_modified": "2024-02-26T19:46:43Z", "last_modified": "2024-02-26T19:46:43Z",
"plugin_version": "0.0.2", "plugin_version": "0.0.2",

View file

@ -4,10 +4,17 @@ set shell := ["devbox", "run"]
set dotenv-load set dotenv-load
export TF_VAR_name := env("APP_NAME") export TF_VAR_name := env("APP_NAME")
export GIT_SHA := `git rev-parse --short HEAD`
# Run server locally # Run server locally
run: run:
flask --app src/server run initdb --username=postgres || true
devbox services start postgresql
python src/app.py
run-docker:
docker build -t local/${APP_NAME}:${GIT_SHA} .
docker run -it --rm -p 8080:8080 local/${APP_NAME}:${GIT_SHA}
dependencies: dependencies:
pip install -r src/requirements.txt pip install -r src/requirements.txt
@ -15,11 +22,30 @@ dependencies:
dependencies-lock: dependencies-lock:
pip freeze -l > src/requirements.txt pip freeze -l > src/requirements.txt
deploy: release:
#!/bin/env bash
USERNAME="00000000-0000-0000-0000-000000000000"
REGISTRY=$(terraform -chdir=terraform output -raw container_registry_name)
terraform-apply: TAG=${REGISTRY}.azurecr.io/develop:${GIT_SHA}-$(date +"%F-%H-%M-%S")
docker build -t ${TAG} .
# For podman support we must use this hacks,
# otherwise az acr login would also do the docker login.
docker login \
--username=${USERNAME} \
--password=$(az acr login --name ${REGISTRY} --expose-token 2>/dev/null | jq -r '.accessToken') \
"${REGISTRY}.azurecr.io"
docker push ${TAG}
echo "Image pushed to ${TAG}"
echo "Modify your .env with TF_VAR_image=${TAG}"
deploy:
terraform -chdir=terraform init terraform -chdir=terraform init
terraform -chdir=terraform apply terraform -chdir=terraform apply
terraform-destroy: destroy:
terraform -chdir=terraform destroy terraform -chdir=terraform destroy

View file

@ -1,11 +1,28 @@
from flask import Flask from flask import Flask, render_template
from database import get_db_connection, migrate
app = Flask(__name__) app = Flask(__name__)
@app.route("/health") @app.route("/health")
def health(): def health():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT 1")
cursor.fetchone()
return "OK" return "OK"
@app.route("/") @app.route("/")
def hello_world(): def hello_world():
return "<p>Hello, World!</p>" conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
print(users)
return render_template("index.html", users=users)
if __name__ == "__main__":
migrate()
app.run(host="0.0.0.0", port=8080)

28
src/database.py Normal file
View file

@ -0,0 +1,28 @@
import os
import psycopg2
conn = None
def get_db_connection():
global conn
if not conn:
conn = psycopg2.connect(
host=os.environ.get("DB_HOST", "localhost"),
database=os.environ.get("DB_NAME", "postgres"),
user=os.environ.get("DB_USER", "postgres"),
password=os.environ.get("DB_PASSWORD", "postgres"),
sslmode=os.environ.get("DB_SSLMODE", "prefer"),
)
return conn
def migrate():
conn = get_db_connection()
cur = conn.cursor()
cur.execute(
"CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(255))"
)
cur.execute("INSERT INTO users (name) VALUES ('John')")
cur.execute("INSERT INTO users (name) VALUES ('Oliver')")
conn.commit()

View file

@ -1,5 +1,5 @@
blinker==1.7.0 blinker==1.7.0
click==8.1.7
Flask==3.0.2 Flask==3.0.2
itsdangerous==2.1.2 itsdangerous==2.1.2
psycopg2-binary==2.9.9
Werkzeug==3.0.1 Werkzeug==3.0.1

10
src/templates/index.html Normal file
View file

@ -0,0 +1,10 @@
<!doctype html>
<title>Hello from Flask</title>
<h1>Here are some users</h1>
<ul>
{% for (id, name) in users %}
<li><strong>{{ id }}:</strong> {{ name}}</li>
{% endfor %}
</ul>

View file

@ -20,3 +20,22 @@ provider "registry.terraform.io/hashicorp/azurerm" {
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
] ]
} }
provider "registry.terraform.io/hashicorp/random" {
version = "3.6.0"
hashes = [
"h1:R5Ucn26riKIEijcsiOMBR3uOAjuOMfI1x7XvH4P6B1w=",
"zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d",
"zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211",
"zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829",
"zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d",
"zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17",
"zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21",
"zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839",
"zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0",
"zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c",
"zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e",
]
}

View file

@ -6,13 +6,20 @@ resource "azurerm_resource_group" "main" {
## ##
# Database # Database
## ##
resource "random_pet" "database_username" {
separator = ""
}
resource "random_password" "database_password" {
length = 16
special = false
}
resource "azurerm_postgresql_server" "main" { resource "azurerm_postgresql_server" "main" {
name = var.name name = var.name
location = azurerm_resource_group.main.location location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name resource_group_name = azurerm_resource_group.main.name
administrator_login = "psqladmin" administrator_login = random_pet.database_username.id
administrator_login_password = "H@Sh1CoR3!" administrator_login_password = random_password.database_password.result
sku_name = "B_Gen5_1" sku_name = "B_Gen5_1"
version = "11" version = "11"
@ -33,6 +40,26 @@ resource "azurerm_container_registry" "main" {
sku = "Basic" sku = "Basic"
} }
data "azurerm_container_registry_scope_map" "main" {
name = "_repositories_pull"
resource_group_name = azurerm_resource_group.main.name
container_registry_name = azurerm_container_registry.main.name
}
resource "azurerm_container_registry_token" "main" {
name = var.name
container_registry_name = azurerm_container_registry.main.name
resource_group_name = azurerm_resource_group.main.name
scope_map_id = data.azurerm_container_registry_scope_map.main.id
}
resource "azurerm_container_registry_token_password" "main" {
container_registry_token_id = azurerm_container_registry_token.main.id
password1 {
}
}
## ##
# Application # Application
## ##
@ -56,12 +83,72 @@ resource "azurerm_container_app" "main" {
resource_group_name = azurerm_resource_group.main.name resource_group_name = azurerm_resource_group.main.name
revision_mode = "Single" revision_mode = "Single"
secret {
name = "registry-token"
value = one(azurerm_container_registry_token_password.main.password1).value
}
registry {
server = azurerm_container_registry.main.login_server
username = azurerm_container_registry_token.main.name
password_secret_name = "registry-token"
}
ingress {
allow_insecure_connections = false
external_enabled = true
target_port = 8080
traffic_weight {
latest_revision = true
percentage = 100
}
}
secret {
name = "db-username"
value = "${random_pet.database_username.id}@${azurerm_postgresql_server.main.name}"
}
secret {
name = "db-password"
value = random_password.database_password.result
}
template { template {
container { container {
name = "maincontainerapp" name = "maincontainerapp"
image = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest" image = var.image
cpu = 0.25 cpu = 0.25
memory = "0.5Gi" memory = "0.5Gi"
env {
name = "DB_HOST"
value = azurerm_postgresql_server.main.fqdn
}
env {
name = "DB_SSLMODE"
value = "require"
}
env {
name = "DB_USER"
secret_name = "db-username"
}
env {
name = "DB_PASSWORD"
secret_name = "db-password"
}
liveness_probe {
path = "/health"
port = 8080
transport = "HTTP"
}
readiness_probe {
path = "/health"
port = 8080
transport = "HTTP"
}
} }
} }
} }

View file

@ -1,3 +1,3 @@
output "database_fqdn" { output "container_registry_name" {
value = azurerm_postgresql_server.main.fqdn value = azurerm_container_registry.main.name
} }

View file

@ -4,8 +4,14 @@ terraform {
source = "hashicorp/azurerm" source = "hashicorp/azurerm"
version = "3.94.0" version = "3.94.0"
} }
random = {
source = "hashicorp/random"
version = "3.6.0"
} }
} }
}
provider "random" {}
provider "azurerm" { provider "azurerm" {
features {} features {}

View file

@ -2,3 +2,9 @@ variable "name" {
description = "The name of the application" description = "The name of the application"
type = string type = string
} }
variable "image" {
description = "The image to run"
type = string
default = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
}