test
This commit is contained in:
parent
883f62b717
commit
25fa79c48c
19
.env.azure.example
Normal file
19
.env.azure.example
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
RG_NAME=zkt-guestbook-rg
|
||||||
|
LOCATION=polandcentral
|
||||||
|
CONTAINERAPPS_ENV=zkt-guestbook-env
|
||||||
|
|
||||||
|
ACR_NAME=acrzktguestbook12345
|
||||||
|
|
||||||
|
FRONTEND_APP=zkt-frontend
|
||||||
|
BACKEND_APP=zkt-backend
|
||||||
|
|
||||||
|
# PostgreSQL Flexible Server.
|
||||||
|
PG_SERVER=zkt-pg-guestbook-12345
|
||||||
|
PG_DB=guestbook
|
||||||
|
PG_ADMIN=guestbookadmin
|
||||||
|
PG_PASSWORD=hesloheslo
|
||||||
|
|
||||||
|
# Lokálne tagy obrazov v ACR
|
||||||
|
FRONTEND_IMAGE=zkt-frontend:latest
|
||||||
|
BACKEND_IMAGE=zkt-backend:latest
|
||||||
7
.env.local.example
Normal file
7
.env.local.example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
LOCAL_POSTGRES_DB=guestbook
|
||||||
|
LOCAL_POSTGRES_USER=guestbook_user
|
||||||
|
LOCAL_POSTGRES_PASSWORD=hesloheslo
|
||||||
|
LOCAL_DB_HOST=db
|
||||||
|
LOCAL_DB_PORT=5432
|
||||||
|
LOCAL_APP_PORT=5000
|
||||||
|
LOCAL_DB_SSLMODE=prefer
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.env.azure
|
||||||
|
.env.local
|
||||||
|
backups/
|
||||||
|
*.log
|
||||||
12
backend/Dockerfile
Normal file
12
backend/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["python", "app.py"]
|
||||||
125
backend/app.py
Normal file
125
backend/app.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": os.getenv("DB_HOST", "db"),
|
||||||
|
"port": int(os.getenv("DB_PORT", "5432")),
|
||||||
|
"dbname": os.getenv("DB_NAME", "guestbook"),
|
||||||
|
"user": os.getenv("DB_USER", "guestbook_user"),
|
||||||
|
"password": os.getenv("DB_PASSWORD", ""),
|
||||||
|
|
||||||
|
"sslmode": os.getenv("DB_SSLMODE", "prefer"),
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_PORT = int(os.getenv("APP_PORT", "5000"))
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
return psycopg2.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
def init_db(retries: int = 20, delay: int = 2) -> None:
|
||||||
|
for attempt in range(1, retries + 1):
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
author VARCHAR(100) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
print("Database initialized.")
|
||||||
|
return
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Database not ready yet (attempt {attempt}/{retries}): {exc}")
|
||||||
|
time.sleep(delay)
|
||||||
|
raise RuntimeError("Could not initialize the database.")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/health", methods=["GET"])
|
||||||
|
def health():
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT 1")
|
||||||
|
cur.fetchone()
|
||||||
|
return jsonify({"status": "ok", "database": "connected"}), 200
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"status": "error", "database": str(exc)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/messages", methods=["GET"])
|
||||||
|
def get_messages():
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT id, author, content, created_at
|
||||||
|
FROM messages
|
||||||
|
ORDER BY created_at DESC, id DESC
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
"id": row[0],
|
||||||
|
"author": row[1],
|
||||||
|
"content": row[2],
|
||||||
|
"created_at": row[3].isoformat(),
|
||||||
|
}
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
return jsonify(data), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/messages", methods=["POST"])
|
||||||
|
def add_message():
|
||||||
|
payload = request.get_json(silent=True) or {}
|
||||||
|
author = (payload.get("author") or "").strip()
|
||||||
|
content = (payload.get("content") or "").strip()
|
||||||
|
|
||||||
|
if not author or not content:
|
||||||
|
return jsonify({"error": "Fields 'author' and 'content' are required."}), 400
|
||||||
|
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO messages (author, content, created_at)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
RETURNING id, author, content, created_at
|
||||||
|
""",
|
||||||
|
(author, content, datetime.utcnow()),
|
||||||
|
)
|
||||||
|
row = cur.fetchone()
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return (
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
"id": row[0],
|
||||||
|
"author": row[1],
|
||||||
|
"content": row[2],
|
||||||
|
"created_at": row[3].isoformat(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
201,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
init_db()
|
||||||
|
app.run(host="0.0.0.0", port=APP_PORT)
|
||||||
2
backend/requirements.txt
Normal file
2
backend/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
flask==3.1.0
|
||||||
|
psycopg2-binary==2.9.10
|
||||||
38
backup-db.sh
Normal file
38
backup-db.sh
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env.azure}"
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Chýba $ENV_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
: "${RG_NAME:?Missing RG_NAME}"
|
||||||
|
: "${PG_SERVER:?Missing PG_SERVER}"
|
||||||
|
: "${PG_DB:?Missing PG_DB}"
|
||||||
|
: "${PG_ADMIN:?Missing PG_ADMIN}"
|
||||||
|
: "${PG_PASSWORD:?Missing PG_PASSWORD}"
|
||||||
|
|
||||||
|
if ! command -v pg_dump >/dev/null 2>&1; then
|
||||||
|
echo "Chýba pg_dump."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$ROOT_DIR/backups"
|
||||||
|
OUT="$ROOT_DIR/backups/${PG_DB}_$(date +%Y%m%d_%H%M%S).sql"
|
||||||
|
|
||||||
|
echo "Vytváram SQL zálohu databázy do: $OUT"
|
||||||
|
PGPASSWORD="$PG_PASSWORD" pg_dump \
|
||||||
|
"host=$PG_SERVER.postgres.database.azure.com port=5432 dbname=$PG_DB user=$PG_ADMIN sslmode=require" \
|
||||||
|
--no-owner \
|
||||||
|
--no-privileges \
|
||||||
|
> "$OUT"
|
||||||
|
|
||||||
|
echo "Záloha hotová: $OUT"
|
||||||
65
docker-compose.yaml
Normal file
65
docker-compose.yaml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: zkt-db
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env.local
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${LOCAL_POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${LOCAL_POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${LOCAL_POSTGRES_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- app-net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${LOCAL_POSTGRES_USER} -d ${LOCAL_POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
container_name: zkt-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env.local
|
||||||
|
environment:
|
||||||
|
DB_HOST: ${LOCAL_DB_HOST}
|
||||||
|
DB_PORT: ${LOCAL_DB_PORT}
|
||||||
|
DB_NAME: ${LOCAL_POSTGRES_DB}
|
||||||
|
DB_USER: ${LOCAL_POSTGRES_USER}
|
||||||
|
DB_PASSWORD: ${LOCAL_POSTGRES_PASSWORD}
|
||||||
|
DB_SSLMODE: ${LOCAL_DB_SSLMODE}
|
||||||
|
APP_PORT: ${LOCAL_APP_PORT}
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- app-net
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
container_name: zkt-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: http://backend:5000
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
networks:
|
||||||
|
- app-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-net:
|
||||||
|
external: true
|
||||||
|
name: zkt_app_net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
external: true
|
||||||
|
name: zkt_pgdata
|
||||||
7
frontend/Dockerfile
Normal file
7
frontend/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM nginx:1.27-alpine
|
||||||
|
COPY nginx.conf.template /etc/nginx/templates/default.conf.template
|
||||||
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
|
|
||||||
|
ENV BACKEND_URL=http://backend:5000
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
185
frontend/index.html
Normal file
185
frontend/index.html
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sk">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ZKT Guestbook</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: #f4f7fb;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 40px auto;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
p.note {
|
||||||
|
color: #4b5563;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
input, textarea, button {
|
||||||
|
font: inherit;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
min-height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #2563eb;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #eef2ff;
|
||||||
|
}
|
||||||
|
.messages {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.empty {
|
||||||
|
color: #6b7280;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f3f4f6;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>ZKT Guestbook</h1>
|
||||||
|
|
||||||
|
<div id="status" class="status">Kontrolujem stav backendu...</div>
|
||||||
|
|
||||||
|
<form id="messageForm">
|
||||||
|
<input id="author" type="text" placeholder="Tvoje meno" required>
|
||||||
|
<textarea id="content" placeholder="Napíš správu..." required></textarea>
|
||||||
|
<button type="submit">Pridať správu</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Správy</h2>
|
||||||
|
<div id="messages" class="messages"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const statusEl = document.getElementById('status');
|
||||||
|
const messagesEl = document.getElementById('messages');
|
||||||
|
const form = document.getElementById('messageForm');
|
||||||
|
|
||||||
|
async function checkHealth() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/health');
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
statusEl.textContent = `Backend je dostupný. Databáza: ${data.database}`;
|
||||||
|
} else {
|
||||||
|
statusEl.textContent = 'Backend nie je dostupný.';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
statusEl.textContent = 'Nepodarilo sa spojiť s backendom.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMessages() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/messages');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
|
messagesEl.innerHTML = '<div class="empty">Zatiaľ tu nie sú žiadne správy.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesEl.innerHTML = data.map(msg => {
|
||||||
|
const date = new Date(msg.created_at).toLocaleString('sk-SK');
|
||||||
|
return `
|
||||||
|
<div class="message">
|
||||||
|
<div class="meta"><strong>${escapeHtml(msg.author)}</strong> • ${date}</div>
|
||||||
|
<div>${escapeHtml(msg.content)}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
} catch (error) {
|
||||||
|
messagesEl.innerHTML = '<div class="empty">Nepodarilo sa načítať správy.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const author = document.getElementById('author').value.trim();
|
||||||
|
const content = document.getElementById('content').value.trim();
|
||||||
|
|
||||||
|
if (!author || !content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/messages', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ author, content })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
form.reset();
|
||||||
|
await loadMessages();
|
||||||
|
} else {
|
||||||
|
alert('Správu sa nepodarilo uložiť.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return value
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll("'", ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHealth();
|
||||||
|
loadMessages();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
frontend/nginx.conf.template
Normal file
23
frontend/nginx.conf.template
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass ${BACKEND_URL}/api/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $proxy_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
212
prepare-app.sh
Normal file
212
prepare-app.sh
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env.azure}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "==> $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "Chyba: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Chýba $ENV_FILE"
|
||||||
|
echo "uprav ACR_NAME, PG_SERVER a PG_PASSWORD."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
: "${RG_NAME:?Missing RG_NAME}"
|
||||||
|
: "${LOCATION:?Missing LOCATION}"
|
||||||
|
: "${CONTAINERAPPS_ENV:?Missing CONTAINERAPPS_ENV}"
|
||||||
|
: "${ACR_NAME:?Missing ACR_NAME}"
|
||||||
|
: "${FRONTEND_APP:?Missing FRONTEND_APP}"
|
||||||
|
: "${BACKEND_APP:?Missing BACKEND_APP}"
|
||||||
|
: "${PG_SERVER:?Missing PG_SERVER}"
|
||||||
|
: "${PG_DB:?Missing PG_DB}"
|
||||||
|
: "${PG_ADMIN:?Missing PG_ADMIN}"
|
||||||
|
: "${PG_PASSWORD:?Missing PG_PASSWORD}"
|
||||||
|
: "${FRONTEND_IMAGE:=zkt-frontend:latest}"
|
||||||
|
: "${BACKEND_IMAGE:=zkt-backend:latest}"
|
||||||
|
|
||||||
|
|
||||||
|
log "Kontrola Azure Container Apps rozsirenia a resource providerov"
|
||||||
|
az extension add --name containerapp --upgrade --yes >/dev/null
|
||||||
|
az provider register --namespace Microsoft.App --wait >/dev/null
|
||||||
|
az provider register --namespace Microsoft.ContainerRegistry --wait >/dev/null
|
||||||
|
az provider register --namespace Microsoft.DBforPostgreSQL --wait >/dev/null
|
||||||
|
az provider register --namespace Microsoft.OperationalInsights --wait >/dev/null
|
||||||
|
|
||||||
|
log "Vytvaram alebo kontrolujem resource group: $RG_NAME ($LOCATION)"
|
||||||
|
az group create --name "$RG_NAME" --location "$LOCATION" >/dev/null
|
||||||
|
|
||||||
|
if ! az acr show --name "$ACR_NAME" --resource-group "$RG_NAME" >/dev/null 2>&1; then
|
||||||
|
log "Vytvaram Azure Container Registry: $ACR_NAME"
|
||||||
|
az acr create \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--name "$ACR_NAME" \
|
||||||
|
--sku Basic \
|
||||||
|
--admin-enabled true >/dev/null
|
||||||
|
else
|
||||||
|
log "ACR uz existuje: $ACR_NAME"
|
||||||
|
az acr update \
|
||||||
|
--name "$ACR_NAME" \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--admin-enabled true >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
ACR_LOGIN_SERVER="$(az acr show --name "$ACR_NAME" --resource-group "$RG_NAME" --query loginServer -o tsv)"
|
||||||
|
ACR_PASSWORD="$(az acr credential show --name "$ACR_NAME" --resource-group "$RG_NAME" --query 'passwords[0].value' -o tsv)"
|
||||||
|
|
||||||
|
log "Build a push backend image do ACR"
|
||||||
|
az acr build --registry "$ACR_NAME" --image "$BACKEND_IMAGE" "$ROOT_DIR/backend"
|
||||||
|
|
||||||
|
log "Build a push frontend image do ACR"
|
||||||
|
az acr build --registry "$ACR_NAME" --image "$FRONTEND_IMAGE" "$ROOT_DIR/frontend"
|
||||||
|
|
||||||
|
if ! az postgres flexible-server show --resource-group "$RG_NAME" --name "$PG_SERVER" >/dev/null 2>&1; then
|
||||||
|
log "Vytvaram Azure Database for PostgreSQL Flexible Server: $PG_SERVER"
|
||||||
|
az postgres flexible-server create \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--name "$PG_SERVER" \
|
||||||
|
--location "$LOCATION" \
|
||||||
|
--admin-user "$PG_ADMIN" \
|
||||||
|
--admin-password "$PG_PASSWORD" \
|
||||||
|
--sku-name Standard_B1ms \
|
||||||
|
--tier Burstable \
|
||||||
|
--storage-size 32 \
|
||||||
|
--version 16 \
|
||||||
|
--public-access 0.0.0.0 \
|
||||||
|
--backup-retention 7 \
|
||||||
|
--yes >/dev/null
|
||||||
|
else
|
||||||
|
log "PostgreSQL server už existuje: $PG_SERVER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Nastavujem firewall pravidlo pre Azure služby k PostgreSQL"
|
||||||
|
az postgres flexible-server firewall-rule create \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--name "$PG_SERVER" \
|
||||||
|
--rule-name allowazureservices \
|
||||||
|
--start-ip-address 0.0.0.0 \
|
||||||
|
--end-ip-address 0.0.0.0 >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
log "Kontrolujem alebo vytvaram databazu: $PG_DB"
|
||||||
|
DB_READY="false"
|
||||||
|
for i in {1..18}; do
|
||||||
|
if az postgres flexible-server db show \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--server-name "$PG_SERVER" \
|
||||||
|
--database-name "$PG_DB" >/dev/null 2>&1; then
|
||||||
|
log "Databaza uz existuje: $PG_DB"
|
||||||
|
DB_READY="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if az postgres flexible-server db create \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--server-name "$PG_SERVER" \
|
||||||
|
--database-name "$PG_DB" >/dev/null 2>&1; then
|
||||||
|
log "Databaza vytvorena: $PG_DB"
|
||||||
|
DB_READY="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "PostgreSQL ešte nemusi byt pripraveny, cakam a skusam znova ($i/18)..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$DB_READY" != "true" ]]; then
|
||||||
|
fail "Nepodarilo sa vytvorit alebo overit databazu $PG_DB. Skontroluj PostgreSQL server a firewall."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! az containerapp env show --name "$CONTAINERAPPS_ENV" --resource-group "$RG_NAME" >/dev/null 2>&1; then
|
||||||
|
log "Vytvaram Container Apps environment: $CONTAINERAPPS_ENV"
|
||||||
|
az containerapp env create \
|
||||||
|
--name "$CONTAINERAPPS_ENV" \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--location "$LOCATION" >/dev/null
|
||||||
|
else
|
||||||
|
log "Container Apps environment už existuje: $CONTAINERAPPS_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
delete_containerapp_if_exists() {
|
||||||
|
local app_name="$1"
|
||||||
|
|
||||||
|
if az containerapp show --name "$app_name" --resource-group "$RG_NAME" >/dev/null 2>&1; then
|
||||||
|
log "Mažem existujúcu Container App: $app_name"
|
||||||
|
az containerapp delete --name "$app_name" --resource-group "$RG_NAME" --yes >/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Nasadzujem backend ako internu Container App"
|
||||||
|
delete_containerapp_if_exists "$BACKEND_APP"
|
||||||
|
az containerapp create \
|
||||||
|
--name "$BACKEND_APP" \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--environment "$CONTAINERAPPS_ENV" \
|
||||||
|
--image "$ACR_LOGIN_SERVER/$BACKEND_IMAGE" \
|
||||||
|
--target-port 5000 \
|
||||||
|
--ingress internal \
|
||||||
|
--transport auto \
|
||||||
|
--registry-server "$ACR_LOGIN_SERVER" \
|
||||||
|
--registry-username "$ACR_NAME" \
|
||||||
|
--registry-password "$ACR_PASSWORD" \
|
||||||
|
--min-replicas 1 \
|
||||||
|
--max-replicas 1 \
|
||||||
|
--cpu 0.25 \
|
||||||
|
--memory 0.5Gi \
|
||||||
|
--secrets db-password="$PG_PASSWORD" \
|
||||||
|
--env-vars \
|
||||||
|
DB_HOST="$PG_SERVER.postgres.database.azure.com" \
|
||||||
|
DB_PORT=5432 \
|
||||||
|
DB_NAME="$PG_DB" \
|
||||||
|
DB_USER="$PG_ADMIN" \
|
||||||
|
DB_PASSWORD=secretref:db-password \
|
||||||
|
DB_SSLMODE=require \
|
||||||
|
APP_PORT=5000 >/dev/null
|
||||||
|
|
||||||
|
log "Nasadzujem frontend ako verejnú Container App"
|
||||||
|
delete_containerapp_if_exists "$FRONTEND_APP"
|
||||||
|
az containerapp create \
|
||||||
|
--name "$FRONTEND_APP" \
|
||||||
|
--resource-group "$RG_NAME" \
|
||||||
|
--environment "$CONTAINERAPPS_ENV" \
|
||||||
|
--image "$ACR_LOGIN_SERVER/$FRONTEND_IMAGE" \
|
||||||
|
--target-port 80 \
|
||||||
|
--ingress external \
|
||||||
|
--transport auto \
|
||||||
|
--registry-server "$ACR_LOGIN_SERVER" \
|
||||||
|
--registry-username "$ACR_NAME" \
|
||||||
|
--registry-password "$ACR_PASSWORD" \
|
||||||
|
--min-replicas 1 \
|
||||||
|
--max-replicas 1 \
|
||||||
|
--cpu 0.25 \
|
||||||
|
--memory 0.5Gi \
|
||||||
|
--env-vars BACKEND_URL="http://$BACKEND_APP" >/dev/null
|
||||||
|
|
||||||
|
FRONTEND_FQDN="$(az containerapp show --name "$FRONTEND_APP" --resource-group "$RG_NAME" --query 'properties.configuration.ingress.fqdn' -o tsv)"
|
||||||
|
|
||||||
|
cat <<MSG
|
||||||
|
|
||||||
|
Hotovo. Aplikacia je dostupna cez HTTPS:
|
||||||
|
https://$FRONTEND_FQDN
|
||||||
|
|
||||||
|
Health check:
|
||||||
|
https://$FRONTEND_FQDN/api/health
|
||||||
|
|
||||||
|
Test v terminali:
|
||||||
|
curl -i https://$FRONTEND_FQDN/api/health
|
||||||
|
|
||||||
|
Ak frontend stale hlasi problem s backendom, teba skontrolovat logy:
|
||||||
|
az containerapp logs show --name $BACKEND_APP --resource-group $RG_NAME --follow
|
||||||
|
az containerapp logs show --name $FRONTEND_APP --resource-group $RG_NAME --follow
|
||||||
|
MSG
|
||||||
32
remove-app.sh
Normal file
32
remove-app.sh
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env.azure}"
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Chýba $ENV_FILE."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
: "${RG_NAME:?Missing RG_NAME}"
|
||||||
|
|
||||||
|
if ! command -v az >/dev/null 2>&1; then
|
||||||
|
echo "Chýba Azure CLI."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! az account show >/dev/null 2>&1; then
|
||||||
|
echo "Nie si prihlaseny v Azure CLI."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Odstranujem celu resource group: $RG_NAME"
|
||||||
|
az group delete --name "$RG_NAME" --yes --no-wait
|
||||||
|
|
||||||
|
echo "Odstranovanie bolo spustene. overenie cez: az group exists --name $RG_NAME"
|
||||||
27
show-logs.sh
Normal file
27
show-logs.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env.azure}"
|
||||||
|
TAIL="${TAIL:-100}"
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Chýba $ENV_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
: "${RG_NAME:?Missing RG_NAME}"
|
||||||
|
: "${FRONTEND_APP:?Missing FRONTEND_APP}"
|
||||||
|
: "${BACKEND_APP:?Missing BACKEND_APP}"
|
||||||
|
|
||||||
|
echo "==> Frontend logy"
|
||||||
|
az containerapp logs show --name "$FRONTEND_APP" --resource-group "$RG_NAME" --tail "$TAIL"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Backend logy"
|
||||||
|
az containerapp logs show --name "$BACKEND_APP" --resource-group "$RG_NAME" --tail "$TAIL"
|
||||||
3
start-app.sh
Normal file
3
start-app.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/start-local.sh"
|
||||||
3
stop-app.sh
Normal file
3
stop-app.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/stop-local.sh"
|
||||||
Loading…
Reference in New Issue
Block a user