Merge pull request #2 from k2patel/Rewriting_testing

Add melange/apko build pipeline and Kubernetes manifests
This commit is contained in:
K2patel authored and GitHub committed 2026-04-16 18:42:28 -04:00
commit d7ed59b0fa
15 files changed
+380 -5

No files matched your search

+40 -1
View File
@@ -36,7 +36,46 @@ jobs:
coverage run -m pytest tests/ -v coverage run -m pytest tests/ -v
coverage report coverage report
build: build-melange-apko:
runs-on: ubuntu-latest
needs: lint-and-test
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate melange signing keypair
uses: chainguard-dev/actions/melange-keygen@main
with:
signing-key-path: melange.rsa
- name: Build package with melange
uses: chainguard-dev/actions/melange-build@main
with:
config: melange.yaml
signing-key-path: melange.rsa
archs: x86_64,aarch64
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish image with apko
uses: chainguard-images/actions/apko-publish@main
with:
config: apko.yaml
archs: x86_64,aarch64
tag: ghcr.io/k2patel/apcupsd-client:latest
keyring-append: melange.rsa.pub
build-docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: lint-and-test needs: lint-and-test
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
+5
View File
@@ -51,3 +51,8 @@ Thumbs.db
# Temporary files # Temporary files
*.tmp *.tmp
*.temp *.temp
# Melange / apko build artifacts
packages/
melange.rsa
melange.rsa.pub
+2
View File
@@ -1,4 +1,6 @@
# syntax=docker/dockerfile:1.6 # syntax=docker/dockerfile:1.6
# NOTE: The primary build method is now melange + apko (see melange.yaml / apko.yaml).
# This Dockerfile is kept for local dev and Docker Compose backward compatibility.
FROM python:3.12.7-slim AS builder FROM python:3.12.7-slim AS builder
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
+31
View File
@@ -0,0 +1,31 @@
contents:
repositories:
- https://dl-cdn.alpinelinux.org/alpine/edge/main
- https://dl-cdn.alpinelinux.org/alpine/edge/community
- '@local ./packages'
packages:
- alpine-baselayout
- busybox
- python3
- apcupsd
- apcupsd-client@local
accounts:
groups:
- groupname: appuser
gid: 10001
users:
- username: appuser
uid: 10001
gid: 10001
run-as: 10001
environment:
PYTHONDONTWRITEBYTECODE: "1"
PYTHONUNBUFFERED: "1"
REDIS_URL: "redis://redis:6379/0"
entrypoint:
command: /usr/bin/start-server
work-dir: /app
+1 -4
View File
@@ -1,7 +1,6 @@
version: '3.9'
services: services:
web: web:
build: . image: docker.io/k2patel/apcupsd-client:latest
container_name: apcups-dashboard container_name: apcups-dashboard
ports: ports:
- "10280:8000" - "10280:8000"
@@ -22,8 +21,6 @@ services:
image: redis:7-alpine image: redis:7-alpine
container_name: redis container_name: redis
restart: always restart: always
# Uncomment to require password (must also set REDIS_URL=redis://:PASS@redis:6379/0):
# command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec", "--requirepass", "${REDIS_PASSWORD}"]
command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec"] command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec"]
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ["CMD", "redis-cli", "ping"]
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: apcupsd-client-config
namespace: apcupsd
labels:
app.kubernetes.io/name: apcupsd-client
app.kubernetes.io/part-of: apcupsd-client
data:
REDIS_URL: "redis://redis:6379/0"
LOG_LEVEL: "info"
TZ: "UTC"
ALLOW_PRIVATE_IPS: "true"
TRUST_PROXY: "true"
RATE_LIMIT_ENABLED: "true"
ADMIN_USERNAME: "admin"
SESSION_MAX_AGE_SECONDS: "86400"
+71
View File
@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: apcupsd-client
namespace: apcupsd
labels:
app.kubernetes.io/name: apcupsd-client
app.kubernetes.io/part-of: apcupsd-client
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: apcupsd-client
template:
metadata:
labels:
app.kubernetes.io/name: apcupsd-client
spec:
securityContext:
runAsUser: 10001
runAsGroup: 10001
runAsNonRoot: true
fsGroup: 10001
containers:
- name: web
image: ghcr.io/k2patel/apcupsd-client:latest
ports:
- containerPort: 8000
protocol: TCP
envFrom:
- configMapRef:
name: apcupsd-client-config
- secretRef:
name: apcupsd-client-secret
livenessProbe:
httpGet:
path: /healthz
port: 8000
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
+11
View File
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- configmap.yaml
- secret.yaml
- redis-statefulset.yaml
- redis-service.yaml
- deployment.yaml
- service.yaml
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: apcupsd
labels:
app.kubernetes.io/part-of: apcupsd-client
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: apcupsd
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/part-of: apcupsd-client
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: redis
ports:
- name: redis
protocol: TCP
port: 6379
targetPort: 6379
+72
View File
@@ -0,0 +1,72 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: apcupsd
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/part-of: apcupsd-client
spec:
serviceName: redis
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: redis
template:
metadata:
labels:
app.kubernetes.io/name: redis
spec:
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: redis
image: redis:7-alpine
command:
- redis-server
- --appendonly
- "yes"
- --appendfsync
- everysec
ports:
- containerPort: 6379
protocol: TCP
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 5
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "250m"
volumeMounts:
- name: redis-data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
+14
View File
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Secret
metadata:
name: apcupsd-client-secret
namespace: apcupsd
labels:
app.kubernetes.io/name: apcupsd-client
app.kubernetes.io/part-of: apcupsd-client
type: Opaque
stringData:
SESSION_SECRET: "CHANGE_ME_TO_A_RANDOM_SECRET"
# Optional — uncomment and set as needed:
# SMTP_PASSWORD: ""
# ADMIN_PASSWORD_HASH: ""
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: apcupsd-client
namespace: apcupsd
labels:
app.kubernetes.io/name: apcupsd-client
app.kubernetes.io/part-of: apcupsd-client
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: apcupsd-client
ports:
- name: http
protocol: TCP
port: 10280
targetPort: 8000
+61
View File
@@ -0,0 +1,61 @@
package:
name: apcupsd-client
version: 1.0.0
epoch: 0
description: FastAPI + Redis UPS monitoring dashboard
copyright:
- license: MIT
environment:
contents:
repositories:
- https://dl-cdn.alpinelinux.org/alpine/edge/main
- https://dl-cdn.alpinelinux.org/alpine/edge/community
packages:
- alpine-baselayout
- busybox
- python3
- python3-dev
- py3-pip
- py3-virtualenv
- gcc
- musl-dev
- libffi-dev
pipeline:
- name: Create virtualenv and install dependencies
runs: |
set -ex
mkdir -p "${{targets.destdir}}/app"
python3 -m virtualenv "${{targets.destdir}}/app/.venv"
"${{targets.destdir}}/app/.venv/bin/pip" install \
--no-cache-dir \
-r requirements.prod.txt
- name: Copy application source
runs: |
set -ex
cp -r app/ "${{targets.destdir}}/app/app/"
- name: Create entrypoint script
runs: |
set -ex
mkdir -p "${{targets.destdir}}/usr/bin"
cat > "${{targets.destdir}}/usr/bin/start-server" <<'SCRIPT'
#!/bin/sh
exec /app/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
SCRIPT
chmod +x "${{targets.destdir}}/usr/bin/start-server"
- name: Fix virtualenv shebangs
runs: |
set -ex
# Rewrite shebangs so they reference the final installed path
find "${{targets.destdir}}/app/.venv/bin" -type f -exec \
sed -i "s|${{targets.destdir}}||g" {} +
data:
- name: runtime-deps
items:
python3: python3
apcupsd: apcupsd
+15
View File
@@ -0,0 +1,15 @@
fastapi==0.115.0
uvicorn[standard]==0.30.6
pyyaml==6.0.2
redis==5.0.7
pydantic==2.9.2
pydantic-settings==2.5.2
jinja2==3.1.4
python-multipart==0.0.9
orjson==3.10.7
passlib[argon2]==1.7.4
itsdangerous==2.2.0
slowapi==0.1.9
python-json-logger==2.0.7
tenacity==9.0.0
prometheus-client==0.21.0