Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on:
push:
branches: ["**"]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: Flask_py
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run unit tests
run: python -m unittest test_rebalancer.py

- name: Compile check
run: python -m py_compile app.py test_rebalancer.py wsgi.py
71 changes: 71 additions & 0 deletions .github/workflows/deploy-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Deploy Preview (GitHub)

on:
push:
branches-ignore:
- main
- master

permissions:
contents: read
packages: write

env:
APP_DIR: Flask_py

jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.image_tag }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build metadata
id: meta
run: |
IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/smart-portfolio-rebalancer:${{ github.sha }}
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT

- name: Build and push image
run: |
docker build -t "${{ steps.meta.outputs.image_tag }}" "${APP_DIR}"
docker push "${{ steps.meta.outputs.image_tag }}"

deploy-preview:
runs-on: ubuntu-latest
needs: build-and-push
if: ${{ secrets.PREVIEW_HOST != '' && secrets.PREVIEW_USER != '' && secrets.PREVIEW_SSH_KEY != '' && secrets.PREVIEW_BASE_URL != '' }}
steps:
- name: Setup SSH key
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.PREVIEW_SSH_KEY }}

- name: Add host key
run: ssh-keyscan -H "${{ secrets.PREVIEW_HOST }}" >> ~/.ssh/known_hosts

- name: Deploy container
run: |
BRANCH_SLUG=$(echo "${GITHUB_REF_NAME}" | tr '[:upper:]' '[:lower:]' | sed 's#[^a-z0-9-]#-#g')
CONTAINER_NAME="rebalancer-${BRANCH_SLUG}"
PREVIEW_URL="https://${BRANCH_SLUG}.${{ secrets.PREVIEW_BASE_URL }}"
ssh "${{ secrets.PREVIEW_USER }}@${{ secrets.PREVIEW_HOST }}" "
docker pull ${{ needs.build-and-push.outputs.image_tag }} &&
docker rm -f ${CONTAINER_NAME} || true &&
docker run -d --name ${CONTAINER_NAME} \
--restart unless-stopped \
-e HOST=0.0.0.0 -e PORT=8000 \
--label traefik.enable=true \
--label traefik.http.routers.${CONTAINER_NAME}.rule=Host\\\`${BRANCH_SLUG}.${{ secrets.PREVIEW_BASE_URL }}\\\` \
--label traefik.http.services.${CONTAINER_NAME}.loadbalancer.server.port=8000 \
${{ needs.build-and-push.outputs.image_tag }}"
echo "Preview deployed at: ${PREVIEW_URL}"
113 changes: 113 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
stages:
- test
- build
- review

variables:
APP_DIR: Flask_py
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
paths:
- .cache/pip

unit_tests:
stage: test
image: python:3.12-slim
before_script:
- cd "$APP_DIR"
- pip install --upgrade pip
- pip install -r requirements.txt
script:
- python -m unittest test_rebalancer.py
- python -m py_compile app.py test_rebalancer.py wsgi.py

build_container:
stage: build
image: docker:27.3.1
services:
- docker:27.3.1-dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$APP_DIR"
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
rules:
- if: '$CI_COMMIT_BRANCH'

# always create a deployment record for branch environments
review_status:
stage: review
image: alpine:3.20
script:
- echo "Review environment registered for branch: $CI_COMMIT_REF_SLUG"
environment:
name: review/$CI_COMMIT_REF_SLUG
url: $CI_PROJECT_URL/-/pipelines/$CI_PIPELINE_ID
rules:
- if: '$CI_COMMIT_BRANCH'

review_app:
stage: review
image: alpine:3.20
needs:
- build_container
before_script:
- apk add --no-cache openssh-client
script:
- |
if [ -z "$PREVIEW_HOST" ] || [ -z "$PREVIEW_USER" ] || [ -z "$PREVIEW_SSH_KEY" ] || [ -z "$PREVIEW_BASE_URL" ]; then
echo "PREVIEW_* variables are not configured. Skipping live host deploy."
exit 0
fi
- eval "$(ssh-agent -s)"
- echo "$PREVIEW_SSH_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$PREVIEW_HOST" >> ~/.ssh/known_hosts
- export REVIEW_CONTAINER="rebalancer-${CI_COMMIT_REF_SLUG}"
- export PREVIEW_URL="https://${CI_ENVIRONMENT_SLUG}.${PREVIEW_BASE_URL}"
- |
ssh "$PREVIEW_USER@$PREVIEW_HOST" "
docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA &&
docker rm -f $REVIEW_CONTAINER || true &&
docker run -d --name $REVIEW_CONTAINER \
--restart unless-stopped \
-e HOST=0.0.0.0 -e PORT=8000 \
--label traefik.enable=true \
--label traefik.http.routers.$REVIEW_CONTAINER.rule=Host\\\`${CI_ENVIRONMENT_SLUG}.${PREVIEW_BASE_URL}\\\` \
--label traefik.http.services.$REVIEW_CONTAINER.loadbalancer.server.port=8000 \
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
"
- echo "Preview deployed at: $PREVIEW_URL"
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.$PREVIEW_BASE_URL
on_stop: stop_review_app
rules:
- if: '$CI_COMMIT_BRANCH'

stop_review_app:
stage: review
image: alpine:3.20
before_script:
- apk add --no-cache openssh-client
script:
- |
if [ -z "$PREVIEW_HOST" ] || [ -z "$PREVIEW_USER" ] || [ -z "$PREVIEW_SSH_KEY" ]; then
echo "PREVIEW_HOST/PREVIEW_USER/PREVIEW_SSH_KEY missing; nothing to stop."
exit 0
fi
- eval "$(ssh-agent -s)"
- echo "$PREVIEW_SSH_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$PREVIEW_HOST" >> ~/.ssh/known_hosts
- export REVIEW_CONTAINER="rebalancer-${CI_COMMIT_REF_SLUG}"
- ssh "$PREVIEW_USER@$PREVIEW_HOST" "docker rm -f $REVIEW_CONTAINER || true"
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
rules:
- if: '$CI_COMMIT_BRANCH'
when: manual
9 changes: 9 additions & 0 deletions Flask_py/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
__pycache__/
*.pyc
*.pyo
*.pyd
.venv/
venv/
.git/
.gitignore
*.log
16 changes: 16 additions & 0 deletions Flask_py/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["gunicorn", "--workers", "2", "--threads", "4", "--timeout", "120", "--bind", "0.0.0.0:8000", "wsgi:app"]
1 change: 1 addition & 0 deletions Flask_py/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn --workers 2 --threads 4 --timeout 120 --bind 0.0.0.0:${PORT:-8000} wsgi:app
Loading