Skip to content
Open
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
56 changes: 56 additions & 0 deletions .github/workflows/release-package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Create and publish a Docker image

on:
push:
branches:
- main

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
build-args: VERSION=${{ steps.meta.outputs.version }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache

14 changes: 8 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
FROM python:3-slim
FROM python:3.13.0a4-slim

# prepare base image
RUN apt update && apt install --yes \
locales \
locales-all \
entr \
git \
wkhtmltopdf \
&& rm -rf /var/lib/apt/lists/*

# prepare application folder
RUN mkdir -p /app/src \
&& mkdir -p /app/shared
RUN mkdir -p /app/src

# install python dependencies
WORKDIR /app
Expand All @@ -29,8 +27,12 @@ COPY ./src /app/src

EXPOSE 8000
ENV SERVICE_PORT=8000

ARG VERSION=local # override at build
ENV VERSION=${VERSION}

CMD ["start"]

VOLUME [ "/app/src" ]
VOLUME [ "/app/shared" ]
VOLUME [ "/app/htmlcov/" ]
VOLUME [ "/app/data/db.sqlite3" ]

23 changes: 23 additions & 0 deletions deploy/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
8 changes: 8 additions & 0 deletions deploy/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v2
name: micro-invoicer
description: Deployment chart for my Django application

version: 0.1.0
type: application

appVersion: "1.2.3"
37 changes: 37 additions & 0 deletions deploy/make-values.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail

context=${1:-local}
domain=${2:-fibonet.ro}

DB_APP_USER=django
DB_APP_PASS=$(pwgen -s1 42)
DB_APP_NAME="microinvoicer_${context}"

DJANGO_SECRET_KEY=$(pwgen -s1 63)
DJANGO_ADMIN_PASS=$(pwgen -s1 42)

cat <<END
useDomain: "${domain}"
useEnvironment: "${context}"

secrets:
dbUser: "django"
dbPassword: "${DB_APP_PASS}"
dbName: "${DB_APP_NAME}"

djangoSecretKey: "${DJANGO_SECRET_KEY}"
END

cat >init-db.sh <<END
# init-db.sh
#!/usr/bin/env bash
set -e

psql -v ON_ERROR_STOP=1 --username postgres <<-EOSQL
CREATE USER ${DB_APP_USER} WITH PASSWORD '${DB_APP_PASS}';
CREATE DATABASE ${DB_APP_NAME};
GRANT ALL PRIVILEGES ON DATABASE ${DB_APP_NAME} TO ${DB_APP_USER};
EOSQL
END
echo "Wrote new init-db.sh"
75 changes: 75 additions & 0 deletions deploy/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
- name: MICRO_SERVER_KEY
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
key: djangoSecretKey
- name: DB_HOST
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
key: dbHost
- name: DB_PORT
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
key: dbPort
- name: DB_NAME
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
key: dbName
- name: DB_USER
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
key: dbUser
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
key: dbPassword
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: ALLOWED_HOSTS
value: "{{ .Release.Name }}-{{ .Values.useEnvironment }}.{{ .Values.useDomain }},$(POD_IP)"
livenessProbe:
httpGet:
path: /healthy/
port: http
initialDelaySeconds: 13
timeoutSeconds: 2
periodSeconds: 13
failureThreshold: 3
readinessProbe:
httpGet:
path: /healthy/
port: http
initialDelaySeconds: 5
timeoutSeconds: 2
periodSeconds: 8
successThreshold: 1
13 changes: 13 additions & 0 deletions deploy/templates/secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Secret
metadata:
name: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
type: Opaque
data:
djangoSecretKey: {{ .Values.secrets.djangoSecretKey | b64enc | quote }}
dbHost: {{ .Values.secrets.dbHost | b64enc | quote }}
dbPort: {{ .Values.secrets.dbPort | b64enc | quote }}
dbUser: {{ .Values.secrets.dbUser | b64enc | quote }}
dbPassword: {{ .Values.secrets.dbPassword | b64enc | quote }}
dbName: {{ .Values.secrets.dbName | b64enc | quote }}

21 changes: 21 additions & 0 deletions deploy/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: v1
kind: Service
metadata:
name: micro-invoicer
annotations:
{{- toYaml .Values.service.annotations | nindent 4 }}

spec:
type: {{ .Values.service.type }}
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8000
- name: https
protocol: TCP
port: 443
targetPort: 8000
selector:
app: "{{ .Release.Name }}-{{ .Values.useEnvironment }}"
20 changes: 20 additions & 0 deletions deploy/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
image:
repository: ghcr.io/kopsha/micro-invoicer:main
pullPolicy: Always

replicaCount: 1

secrets:
dbUser: "django"
dbHost: "store-postgresql"
dbPort: "5432"

service:
type: LoadBalancer
externalTrafficPolicy: Cluster
annotations:
service.beta.kubernetes.io/do-loadbalancer-protocol: "http"
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
service.beta.kubernetes.io/do-loadbalancer-redirect-http-to-https: "true"
# service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: "true"
service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "false"
6 changes: 5 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ version: "3"

services:
micro-invoicer:
build: ./
build: .
ports:
- 8000:8000
volumes:
- ./src:/app/src
- ./data/db.sqlite3:/app/data/db.sqlite3
environment:
- DEBUG=on

4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pytest
sqlparse
psycopg2-binary
django
django-registration
django-material
django-countries
pdfkit
python-dateutil
requests
requests

1 change: 0 additions & 1 deletion src/microinvoicer/micro_timesheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ def previous_month():


def create_random_tasks(activity, how_many, hours):

names = pick_task_names(flavor=activity["flavor"], count=how_many)
durations = split_duration(duration=hours, count=how_many)
dates = compute_start_dates(activity["start_date"], durations)
Expand Down
4 changes: 1 addition & 3 deletions src/microinvoicer/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""How about now."""
from datetime import date, timedelta, datetime
from datetime import date, timedelta
from django.http import FileResponse
from django.urls import reverse_lazy
from django.views.generic import TemplateView
Expand Down Expand Up @@ -59,7 +58,6 @@ def get_context_data(self, **kwargs):


class ReportView(LoginRequiredMixin, TemplateView):

template_name = "report.html"

def get_context_data(self, **kwargs):
Expand Down
12 changes: 12 additions & 0 deletions src/microtools/healthy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.http import JsonResponse
from django.conf import settings


def health_check(request):
response = dict(
status="healthy",
version=settings.VERSION,
debug=settings.DEBUG,
message="Service is up and running",
)
return JsonResponse(response, status=200)
16 changes: 11 additions & 5 deletions src/microtools/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"""
import os

TRUEISH = {"true", "True", "yes", "1", "on"}
DEBUG = os.environ.get("DEBUG", "False") in TRUEISH

DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost").split(",")
VERSION = os.environ.get("VERSION", "develop")

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand Down Expand Up @@ -52,9 +54,13 @@
WSGI_APPLICATION = "microtools.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ.get("DB_HOST"),
"PORT": os.environ.get("DB_PORT"),
"NAME": os.environ.get("DB_NAME"),
"USER": os.environ.get("DB_USER"),
"PASSWORD": os.environ.get("DB_PASSWORD"),
},
}

AUTH_PASSWORD_VALIDATORS = [
Expand Down
Loading