51 Commits

Author SHA1 Message Date
Marcin-Ramotowski b56efa4fa3 Automatyczna zmiana: 1753630035 2025-07-27 17:29:21 +02:00
Marcin-Ramotowski bb0a6e5dd9 Automatyczna zmiana: 1753629562 2025-07-27 17:29:13 +02:00
Marcin-Ramotowski 072cf7e9aa Automatyczna zmiana: 1753628608 2025-07-27 17:03:34 +02:00
Marcin-Ramotowski 17add103eb Automatyczna zmiana: 1753627976 2025-07-27 16:53:00 +02:00
Marcin-Ramotowski b887fd46b0 Automatyczna zmiana: 1753626456 2025-07-27 16:32:56 +02:00
Marcin-Ramotowski abf126f31e Automatyczna zmiana: 1753625912 2025-07-27 16:31:17 +02:00
Marcin-Ramotowski 0d6a52d2f9 Automatyczna zmiana: 1753625345 2025-07-27 16:09:10 +02:00
Marcin-Ramotowski e080594dcb Automatyczna zmiana: 1753624962 2025-07-27 16:03:26 +02:00
Marcin-Ramotowski 6a11df64b0 Automatyczna zmiana: 1753624408 2025-07-27 15:53:34 +02:00
Marcin-Ramotowski fe9a68023a Automatyczna zmiana: 1753624006 2025-07-27 15:46:56 +02:00
Marcin-Ramotowski 80210e67ea Automatyczna zmiana: 1753623624 2025-07-27 15:40:34 +02:00
Marcin-Ramotowski 300adef0f9 Updated path in repo 2025-07-27 14:56:59 +02:00
Marcin-Ramotowski 492df7e45c Changed deploy repo branch 2025-07-27 14:48:25 +02:00
Marcin-Ramotowski af829bc5bb Added overwrite output lines during waiting
ci/woodpecker/push/build Pipeline was successful
2025-07-26 16:47:54 +02:00
Marcin-Ramotowski 66e2a0e3b9 Merge branch 'dev' into woodpecker
ci/woodpecker/push/build Pipeline was successful
2025-07-26 16:35:10 +02:00
Marcin-Ramotowski 6bfcbb1c96 Automatyczna zmiana: 1753539316
ci/woodpecker/push/build Pipeline was successful
2025-07-26 16:15:16 +02:00
Marcin-Ramotowski 7072671564 Added dockerd command
ci/woodpecker/push/build Pipeline was successful
2025-07-22 19:36:56 +02:00
Marcin-Ramotowski a2c2e39a1b Corrected commands variables expansion
ci/woodpecker/push/build Pipeline failed
2025-07-22 19:30:03 +02:00
Marcin-Ramotowski 55eeec730d Added userns-mode annotation
ci/woodpecker/push/build Pipeline failed
2025-07-21 15:39:06 +02:00
Marcin-Ramotowski e6c9675abc Added sysbox as runtimeClass
ci/woodpecker/push/build Pipeline failed
2025-07-20 11:15:32 +02:00
Marcin-Ramotowski ebf713f75f Removed Goss from pipeline; added GitOps commit 2025-07-20 11:01:12 +02:00
Marcin-Ramotowski 13f098fc7c Removed Goss 2025-07-20 10:02:27 +02:00
Marcin-Ramotowski 8cf8a92f8d Removed Jenkinsfile 2025-07-20 10:01:57 +02:00
Marcin-Ramotowski 571a45d3de Added script to trigger and measure deployment time 2025-07-20 10:00:29 +02:00
Marcin-Ramotowski 560090319a Added build args to Dockerfile 2025-07-20 09:59:53 +02:00
Marcin-Ramotowski e6c68f839a Added script to trigger and measure deployment time 2025-07-20 09:54:38 +02:00
Marcin-Ramotowski fcb93a5cf8 Added build args to Dockerfile 2025-07-20 09:54:31 +02:00
Marcin-Ramotowski c9b1dac864 Added endpoint to fetch app version 2025-07-03 22:47:21 +02:00
Marcin-Ramotowski cd4ab3fd27 Handled more errors during db initialization 2025-06-12 18:42:07 +00:00
Marcin-Ramotowski 301cf5922e Changed docker image base to Alpine and added curl 2025-06-11 22:15:37 +00:00
Marcin-Ramotowski 479ec4f917 Added healthcheck 2025-06-11 22:04:35 +00:00
Marcin-Ramotowski 3f40a6126c Added more descriptions of functions 2025-06-11 20:04:04 +00:00
Marcin-Ramotowski dd9e9ce110 Improved function body 2025-06-11 19:57:15 +00:00
Marcin-Ramotowski d3d3c98f99 Moved wait_for_db function to utils module 2025-06-11 19:48:58 +00:00
Marcin-Ramotowski 9e010ed389 Implemented waiting for db readiness 2025-06-11 19:43:47 +00:00
Marcin-Ramotowski 636a382cf5 Deleted jenkins pipeline from main branch 2025-06-11 17:13:27 +00:00
Marcin-Ramotowski cefb8eba4d Added missing dgoss installation
ci/woodpecker/push/build Pipeline was successful
2025-05-25 20:09:14 +00:00
Marcin-Ramotowski 1e54ba614a Corrected output path for dgoss command
ci/woodpecker/push/build Pipeline failed
2025-05-25 20:05:49 +00:00
Marcin-Ramotowski 7469609600 Removed redundant quote around dgoss command
ci/woodpecker/push/build Pipeline failed
2025-05-25 20:01:31 +00:00
Marcin-Ramotowski b7a1fcfe49 Added double quotes around dgoss run command 2025-05-25 20:00:13 +00:00
Marcin-Ramotowski a9743ecfbe Restored old procedure for Goss tests 2025-05-25 19:59:21 +00:00
Marcin-Ramotowski 76f33f50f5 Extended time to dockerd start
ci/woodpecker/push/build Pipeline failed
2025-05-25 19:54:30 +00:00
Marcin-Ramotowski 203a81573d Added dockerd start in the background
ci/woodpecker/push/build Pipeline failed
2025-05-25 19:43:19 +00:00
Marcin-Ramotowski 383e906102 Corrected image name
ci/woodpecker/push/build Pipeline failed
2025-05-25 19:36:16 +00:00
Marcin-Ramotowski e7330f07ee Returned to privileged mode
ci/woodpecker/push/build Pipeline failed
2025-05-25 19:34:22 +00:00
Marcin-Ramotowski c996be5953 Modified building step 2025-05-25 17:44:17 +00:00
Marcin-Ramotowski bf60011948 Added double quotes around command to run Dgoss 2025-05-25 17:17:11 +00:00
Marcin-Ramotowski 82d5962020 Added steps to build, test and push docker image 2025-05-25 17:14:18 +00:00
Marcin-Ramotowski 1254b036f5 Added cd to api directory
ci/woodpecker/push/build Pipeline was successful
2025-05-25 16:54:15 +00:00
Marcin-Ramotowski 6d84bf694e Added python tests to Woodpecker Workflow
ci/woodpecker/push/build Pipeline failed
2025-05-25 16:43:44 +00:00
Marcin-Ramotowski c2df9d136e Test first Woodpecker workflow
ci/woodpecker/push/my-first-workflow Pipeline failed
2025-05-25 16:19:25 +00:00
11 changed files with 218 additions and 83 deletions
+71
View File
@@ -0,0 +1,71 @@
when:
- event: [push, manual]
branch: woodpecker
steps:
- name: code-tests
image: python:3.11.7-alpine
commands:
- cd api
- python3 -m venv env
- source env/bin/activate
- pip install -r requirements.txt pytest
- python3 -m pytest --junit-xml=pytest_junit.xml
- name: build-and-push
image: marcin00.azurecr.io/azure-cli-docker:slim-bookworm
environment:
ACR_NAME: marcin00
CLIENT_ID: c302726f-fafb-4143-94c1-67a70975574a
commands:
- dockerd &
- export DOCKER_IMAGE=marcin00.azurecr.io/user-microservice:${CI_COMMIT_SHA}
- docker build -t $DOCKER_IMAGE --build-arg APP_VERSION=${CI_COMMIT_SHA} --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") .
- az login --identity --client-id $CLIENT_ID
- az acr login --name $ACR_NAME
- docker push $DOCKER_IMAGE
backend_options:
kubernetes:
annotations:
io.kubernetes.cri-o.userns-mode: "auto:size=65536"
runtimeClassName: sysbox-runc
- name: gitops-commit
image: alpine/git
environment:
DEPLOY_REPO_URL: ssh://git@srv22.mikr.us:20343/pikram/user-microservice-deploy.git
DEPLOY_REPO_BRANCH: woodpecker-fluxcd-deploy
GITEA_DEPLOY_KEY:
from_secret: gitea-deploy-key
GITEA_KNOWN_HOST:
from_secret: gitea-known-host
commands:
- mkdir -p ~/.ssh
- echo "$GITEA_KNOWN_HOST" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- echo "$GITEA_DEPLOY_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- git config --global user.name "woodpecker[bot]"
- git config --global user.email "woodpecker@marcin00.pl"
- git clone $DEPLOY_REPO_URL --branch $DEPLOY_REPO_BRANCH
- cd user-microservice-deploy/apps/user-microservice
- |
awk -v commit="$CI_COMMIT_SHA" '
$0 ~ /name:[[:space:]]*api/ { in_api_container = 1; print; next }
in_api_container && $0 ~ /^[[:space:]]*image:[[:space:]]*/ {
sub(/:[^:[:space:]]+$/, ":" commit)
in_api_container = 0
print
next
}
{ print }
' deploy.yaml > deploy.tmp && mv deploy.tmp deploy.yaml
- git add deploy.yaml
- 'git diff-index --quiet HEAD || git commit -m "WOODPECKER: Changed deployed version to $CI_COMMIT_SHA"'
- git push origin $DEPLOY_REPO_BRANCH
+14 -1
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
Vendored
-72
View File
@@ -1,72 +0,0 @@
pipeline {
agent any
environment {
DOCKER_REGISTRY_URL = 'marcin00.azurecr.io'
DOCKER_IMAGE = "${DOCKER_REGISTRY_URL}/user-microservice:${GIT_COMMIT}"
ACR_NAME = 'marcin00'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Test python app') {
steps {
script {
dir('api') {
sh '''
python3 -m venv env
source env/bin/activate
pip install -r requirements.txt pytest
python3 -m pytest --junit-xml=pytest_junit.xml
'''
}
}
}
post {
always {
junit testResults: '**/*pytest_junit.xml'
}
}
}
stage('Build & test docker image') {
steps {
script {
appImage = docker.build("${DOCKER_IMAGE}")
sh label: 'Install dgoss', script: '''
curl -s -L https://github.com/aelsabbahy/goss/releases/latest/download/goss-linux-amd64 -o goss
curl -s -L https://github.com/aelsabbahy/goss/releases/latest/download/dgoss -o dgoss
chmod +rx *goss
'''
withEnv(['GOSS_OPTS=-f junit', 'GOSS_PATH=./goss', 'GOSS_SLEEP=3', 'SQLALCHEMY_DATABASE_URI=sqlite:///:memory:']) {
sh label: 'run image tests', script: './dgoss run -e SQLALCHEMY_DATABASE_URI=sqlite:///:memory: ${DOCKER_IMAGE} > goss_junit.xml'
}
}
}
post {
always {
junit testResults: '**/*goss_junit.xml'
}
}
}
stage('Deploy') {
steps {
script {
sh '''
az login --identity
az acr login --name ${ACR_NAME}
docker push ${DOCKER_IMAGE}
'''
}
}
}
}
post {
cleanup {
script { cleanWs() }
}
}
}
+4 -1
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
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
+20 -1
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():
+8
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")
})
+65
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 %ds: %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... %ds" "$WAITED"
fi
done
# Żeby kursor przeszedł do nowej linii po zakończeniu
echo ""
+15
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
-8
View File
@@ -1,8 +0,0 @@
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
process:
python3:
running: true
+1
View File
@@ -0,0 +1 @@
1753630035