28 Commits

Author SHA1 Message Date
e62cc94607 Automatyczna zmiana: 1754148549 2025-08-02 17:29:09 +02:00
f9d73ec304 Automatyczna zmiana: 1754148346 2025-08-02 17:25:46 +02:00
90460d78f2 Automatyczna zmiana: 1754148022 2025-08-02 17:20:22 +02:00
31868e928b Automatyczna zmiana: 1754148012 2025-08-02 17:20:12 +02:00
8a75354fec Automatyczna zmiana: 1754147817 2025-08-02 17:16:57 +02:00
b449e20d0e Automatyczna zmiana: 1754147604 2025-08-02 17:13:24 +02:00
fc94078c80 Automatyczna zmiana: 1754147379 2025-08-02 17:09:39 +02:00
944ee22cc7 Automatyczna zmiana: 1754147161 2025-08-02 17:06:01 +02:00
d5dc7d91e6 Automatyczna zmiana: 1754146942 2025-08-02 17:02:22 +02:00
a13187a87d Automatyczna zmiana: 1754146738 2025-08-02 16:58:58 +02:00
b66a5fd19f Automatyczna zmiana: 1754146565 2025-08-02 16:56:05 +02:00
80ce5e1d2d Automatyczna zmiana: 1754146380 2025-08-02 16:53:00 +02:00
fdf79aed50 Automatyczna zmiana: 1754146163 2025-08-02 16:49:23 +02:00
bf8dc5045f Automatyczna zmiana: 1754145967 2025-08-02 16:46:07 +02:00
972026ede3 Automatyczna zmiana: 1754145755 2025-08-02 16:42:35 +02:00
272c201aa4 Automatyczna zmiana: 1754145553 2025-08-02 16:39:13 +02:00
7ed39517e7 Automatyczna zmiana: 1754145378 2025-08-02 16:36:18 +02:00
3ec81ae920 Automatyczna zmiana: 1754145162 2025-08-02 16:32:42 +02:00
358d5712cf Automatyczna zmiana: 1754144989 2025-08-02 16:29:49 +02:00
2c56cd6a2b Automatyczna zmiana: 1754144795 2025-08-02 16:26:35 +02:00
5155356aa0 Automatyczna zmiana: 1754144601 2025-08-02 16:23:21 +02:00
c2af6c6ca8 Automatyczna zmiana: 1754144356 2025-08-02 16:19:16 +02:00
49d7f8a500 Added missing dev functionalities for Docker files from dev branch 2025-08-02 16:16:05 +02:00
e32403dd86 Added execute permissions to deployment_timer script 2025-08-02 16:14:28 +02:00
64605796b2 Added development functionalities from dev branch 2025-08-02 15:54:42 +02:00
a652a0bddb Added script to trigger and measure deployment time 2025-08-02 15:32:36 +02:00
30ba18cace Corrected event source name 2025-08-02 15:11:20 +02:00
e297121e93 Added second webhook to separate deploy repo 2025-08-02 14:52:42 +02:00
12 changed files with 273 additions and 9 deletions

View File

@ -1,5 +1,18 @@
FROM python:3.11.7-slim-bookworm FROM python:3.11.7-alpine
# Wersja i data builda jako build-arg
ARG APP_VERSION=unknown
ARG BUILD_DATE=unknown
# Ustawiamy zmienne w ENV, by były dostępne w kontenerze
ENV APP_VERSION=$APP_VERSION
ENV BUILD_DATE=$BUILD_DATE
WORKDIR /app WORKDIR /app
COPY api . COPY api .
RUN apk add --no-cache curl
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
CMD python3 app.py
CMD python3 app.py

View File

@ -4,7 +4,8 @@ from flask_jwt_extended import JWTManager
from jwt import ExpiredSignatureError from jwt import ExpiredSignatureError
from models import db, RevokedToken from models import db, RevokedToken
import os import os
from utils import init_db from tech_views import tech_bp
from utils import init_db, wait_for_db
from views import user_bp from views import user_bp
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@ -26,6 +27,7 @@ def create_app(config_name="default"):
# Blueprints registration # Blueprints registration
app.register_blueprint(user_bp) app.register_blueprint(user_bp)
app.register_blueprint(tech_bp)
# Database and JWT initialization # Database and JWT initialization
db.init_app(app) db.init_app(app)
@ -53,6 +55,7 @@ def create_app(config_name="default"):
# Fill database by initial values (only if we are not testing) # Fill database by initial values (only if we are not testing)
with app.app_context(): with app.app_context():
wait_for_db(max_retries=100)
db.create_all() db.create_all()
if config_name != "testing": if config_name != "testing":
init_db() init_db()

20
api/tech_views.py Normal file
View File

@ -0,0 +1,20 @@
from flask import Blueprint, jsonify
from models import db
from sqlalchemy import text
from utils import db_ready
# Blueprint with technical endpoints
tech_bp = Blueprint('tech_bp', __name__)
@tech_bp.route('/health', methods=['GET'])
def health_check():
"Check if service works and database is functional"
try:
with db.engine.connect() as connection:
connection.execute(text("SELECT 1"))
return jsonify(status="healthy"), 200
except Exception:
if db_ready:
return jsonify(status="unhealthy"), 500
else:
return jsonify(status="starting"), 503

View File

@ -2,17 +2,22 @@ from flask import abort
from flask_jwt_extended import get_jwt_identity from flask_jwt_extended import get_jwt_identity
from models import User, db from models import User, db
import os import os
from sqlalchemy import text
from sqlalchemy.exc import DatabaseError, InterfaceError
import time
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
db_ready = False
def admin_required(user_id, message='Access denied.'): def admin_required(user_id, message='Access denied.'):
"Check if common user try to make administrative action."
user = db.session.get(User, user_id) user = db.session.get(User, user_id)
if user is None or user.role != "Administrator": if user is None or user.role != "Administrator":
abort(403, message) abort(403, message)
def validate_access(owner_id, message='Access denied.'): def validate_access(owner_id, message='Access denied.'):
# Check if user try to access or edit resource that does not belong to them "Check if user try to access or edit resource that does not belong to them."
logged_user_id = int(get_jwt_identity()) logged_user_id = int(get_jwt_identity())
logged_user_role = db.session.get(User, logged_user_id).role logged_user_role = db.session.get(User, logged_user_id).role
if logged_user_role != "Administrator" and logged_user_id != owner_id: if logged_user_role != "Administrator" and logged_user_id != owner_id:
@ -27,6 +32,20 @@ def get_user_or_404(user_id):
return user return user
def wait_for_db(max_retries):
"Try to connect with database <max_retries> times."
global db_ready
for _ in range(max_retries):
try:
with db.engine.connect() as connection:
connection.execute(text("SELECT 1"))
db_ready = True
return
except DatabaseError | InterfaceError:
time.sleep(3)
raise Exception("Failed to connect to database.")
def init_db(): def init_db():
"""Create default admin account if database is empty""" """Create default admin account if database is empty"""
with db.session.begin(): with db.session.begin():

View File

@ -2,6 +2,7 @@ from flask import Blueprint, jsonify, request, abort
from flask_jwt_extended import create_access_token, set_access_cookies, jwt_required, \ from flask_jwt_extended import create_access_token, set_access_cookies, jwt_required, \
verify_jwt_in_request, get_jwt_identity, unset_jwt_cookies, get_jwt verify_jwt_in_request, get_jwt_identity, unset_jwt_cookies, get_jwt
from models import db, RevokedToken, User from models import db, RevokedToken, User
import os
from utils import admin_required, validate_access, get_user_or_404 from utils import admin_required, validate_access, get_user_or_404
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
@ -110,3 +111,10 @@ def user_logout():
response = jsonify({"msg": "User logged out successfully."}) response = jsonify({"msg": "User logged out successfully."})
unset_jwt_cookies(response) unset_jwt_cookies(response)
return response return response
@user_bp.route('/version', methods=['GET'])
def version():
return jsonify({
"version": os.getenv("APP_VERSION", "unknown"),
"build_time": os.getenv("BUILD_DATE", "unknown")
})

View File

@ -0,0 +1,109 @@
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: webhook-deploy
namespace: argo-events
spec:
template:
serviceAccountName: operate-workflow-sa
dependencies:
- name: gitea-push
eventSourceName: webhook
eventName: user-microservice-deploy
triggers:
- template:
name: deploy-user-microservice
k8s:
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: deploy-user-microservice-
spec:
entrypoint: main
serviceAccountName: operate-workflow-sa
volumeClaimTemplates:
- metadata:
name: workspace
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 128Mi
templates:
- name: main
steps:
- - name: checkout
template: checkout
- - name: deploy
template: deploy
- name: checkout
container:
image: alpine/git
command: [sh, -c]
workingDir: /workspace
env:
- name: REPO_URL
value: https://gitea.marcin00.pl/pikram/user-microservice-deploy.git
- name: REPO_BRANCH
value: argo-deploy
args:
- |
git clone --depth 1 --branch "${REPO_BRANCH}" --single-branch "${REPO_URL}" repo
volumeMounts:
- name: workspace
mountPath: /workspace
- name: deploy
container:
image: marcin00.azurecr.io/azure-cli-kubectl:latest
command: [sh, -c]
workingDir: /workspace/repo
env:
- name: CLIENT_ID
value: "c302726f-fafb-4143-94c1-67a70975574a"
- name: CLUSTER_NAME
value: "build"
- name: RESOURCE_GROUP
value: "tst-aks-rg"
- name: DEPLOY_FILES
value: "namespace.yaml secret-store.yaml deploy.yaml ingress.yaml"
- name: DEPLOYMENT
value: "api"
- name: NAMESPACE
value: "user-microservice"
- name: HEALTHCHECK_URL
value: "https://user-microservice.marcin00.pl/health"
args:
- |
echo "===> Logging in to Azure"
az login --identity --client-id $CLIENT_ID
az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --overwrite-existing
kubelogin convert-kubeconfig -l azurecli
echo "===> Applying Kubernetes manifests"
for file in $DEPLOY_FILES; do
kubectl apply -f "$file"
done
echo "===> Waiting for deployment to complete"
kubectl rollout status deployment/$DEPLOYMENT -n $NAMESPACE --timeout=60s
echo "===> Running health check"
for i in $(seq 1 120); do
if curl -sf $HEALTHCHECK_URL; then
echo "Health check OK"
exit 0
else
echo "Health check failed. Retry $i..."
sleep 5
fi
done
echo "Health check failed"
exit 1
volumeMounts:
- name: workspace
mountPath: /workspace

View File

@ -9,7 +9,7 @@ spec:
dependencies: dependencies:
- name: gitea-push - name: gitea-push
eventSourceName: webhook eventSourceName: webhook
eventName: test-hook eventName: user-microservice
triggers: triggers:
- template: - template:
name: trigger-build-workflow name: trigger-build-workflow

View File

@ -9,7 +9,11 @@ spec:
- port: 12000 - port: 12000
targetPort: 12000 targetPort: 12000
webhook: webhook:
test-hook: user-microservice:
endpoint: /gitea-hook endpoint: /user-microservice
method: POST method: POST
port: "12000" port: "12000"
user-microservice-deploy:
endpoint: /user-microservice-deploy
method: POST
port: "12000"

View File

@ -11,7 +11,14 @@ spec:
- host: argo-hook.marcin00.pl - host: argo-hook.marcin00.pl
http: http:
paths: paths:
- path: /gitea-hook - path: /user-microservice
pathType: Prefix
backend:
service:
name: webhook-eventsource-svc
port:
number: 12000
- path: /user-microservice-deploy
pathType: Prefix pathType: Prefix
backend: backend:
service: service:

65
deployment_timer.sh Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
# === KONFIGURACJA ===
APP_URL="https://user-microservice.marcin00.pl/version"
MARKER_FILE="version_marker.txt"
OUTPUT_FILE="deployment_times.csv"
CHECK_INTERVAL=1 # sekundy
# === POBRANIE AKTUALNEJ WERSJI APLIKACJI ===
echo "[INFO] Pobieranie aktualnej wersji z /version..."
OLD_VERSION=$(curl -s "$APP_URL" | jq -r '.version')
if [[ -z "$OLD_VERSION" ]]; then
echo "[ERROR] Nie udało się pobrać aktualnej wersji aplikacji."
exit 1
fi
echo "[INFO] Aktualna wersja: $OLD_VERSION"
# === Modyfikacja pliku, commit i push ===
TIMESTAMP=$(date +%s)
echo "$TIMESTAMP" > "$MARKER_FILE"
git add "$MARKER_FILE"
git commit -m "Automatyczna zmiana: $TIMESTAMP"
START_TIME=$(date +%s)
echo "[INFO] Wykonuję git push..."
git push
if [[ $? -ne 0 ]]; then
echo "[ERROR] Push nie powiódł się."
exit 1
fi
echo "[INFO] Oczekiwanie na wdrożenie nowej wersji..."
# === Odpytywanie endpointa /version ===
WAITED=0
echo "[WAIT] Oczekiwanie na nową wersję..."
while true; do
sleep $CHECK_INTERVAL
WAITED=$((WAITED + CHECK_INTERVAL))
NEW_VERSION=$(curl -s "$APP_URL" | jq -r '.version')
if [[ "$NEW_VERSION" != "$OLD_VERSION" ]]; then
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
# Nadpisujemy linię z licznikiem
printf "\r[INFO] Nowa wersja wdrożona po %d healtcheck próbach: %s\n" "$WAITED" "$NEW_VERSION"
echo "[INFO] Czas wdrożenia: $DURATION sekund"
echo "$START_TIME,$END_TIME,$DURATION,$OLD_VERSION,$NEW_VERSION" >> "$OUTPUT_FILE"
break
else
# Nadpisujemy TYLKO linię z licznikiem
printf "\r[WAIT] Czekam... wykonano %d healtcheck prób" "$WAITED"
fi
done
# Żeby kursor przeszedł do nowej linii po zakończeniu
echo ""

View File

@ -7,9 +7,24 @@ services:
build: . build: .
env_file: env_file:
- api/.env - api/.env
ports:
- 80:80
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
db: db:
container_name: db container_name: db
hostname: db hostname: db
image: mysql:latest image: mysql:latest
env_file: env_file:
- db/.env - db/.env
ports:
- 3306:3306
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5

1
version_marker.txt Normal file
View File

@ -0,0 +1 @@
1754148549