diff --git a/.github/workflows/cd-deploy.yml b/.github/workflows/cd-deploy.yml new file mode 100644 index 00000000..49e03e88 --- /dev/null +++ b/.github/workflows/cd-deploy.yml @@ -0,0 +1,82 @@ +# .github/workflows/cd-deploy.yml +name: CD – Build, Push & Deploy + +on: + push: + branches: [ listOfMed ] + +permissions: + contents: read + packages: write + +env: + IMAGE_REG: ghcr.io/${{ github.repository_owner }} + +jobs: + build: + name: Build & Push Production Images + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.set-vars.outputs.TAG }} + steps: + - uses: actions/checkout@v3 + + - name: Set version tag + id: set-vars + run: | + # e.g. v1.2.3 or commit SHA fallback + if [[ "${GITHUB_REF##*/}" =~ ^v[0-9] ]]; then + echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT + else + echo "TAG=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + fi + + - name: Login to registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & push backend + uses: docker/build-push-action@v3 + with: + context: server + file: server/Dockerfile.prod + push: true + tags: | + ${{ env.IMAGE_REG }}/balancer-backend:${{ needs.build.outputs.tag }} + ${{ env.IMAGE_REG }}/balancer-backend:latest + + - name: Build & push frontend + uses: docker/build-push-action@v3 + with: + context: frontend + file: frontend/Dockerfile + push: true + tags: | + ${{ env.IMAGE_REG }}/balancer-frontend:${{ needs.build.outputs.tag }} + ${{ env.IMAGE_REG }}/balancer-frontend:latest + + # deploy: + # name: Deploy to Kubernetes + # needs: build + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + + # - name: Set up kubectl + # # or use azure/setup-kubectl, google-github-actions/setup-gcloud, etc. + # uses: azure/setup-kubectl@v3 + # with: + # version: 'latest' + # # supply your kubeconfig via a secret + # kubeconfig: ${{ secrets.KUBE_CONFIG_DATA }} + + # - name: Update Helm chart values + # run: | + # helm upgrade --install balancer ./charts/balancer \ + # --namespace production \ + # --create-namespace \ + # --set backend.image.tag=${{ needs.build.outputs.tag }} \ + # --set frontend.image.tag=${{ needs.build.outputs.tag }} diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 00000000..61b1f0be --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,49 @@ +name: CI – Build & Push PR Images + +on: + pull_request: + branches: [ listOfMed ] + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Compute lowercase owner + id: vars + run: | + owner=$(echo "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]') + echo "owner=$owner" >> $GITHUB_OUTPUT + + - name: Log in to registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ steps.vars.outputs.owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & push backend + uses: docker/build-push-action@v3 + with: + context: server + file: server/Dockerfile.prod + push: true + tags: | + ghcr.io/${{ steps.vars.outputs.owner }}/balancer-backend:pr-${{ github.event.number }} + ghcr.io/${{ steps.vars.outputs.owner }}/balancer-backend:pr-${{ github.event.number }}-latest + + - name: Build & push frontend + uses: docker/build-push-action@v3 + with: + context: frontend + file: frontend/Dockerfile + push: true + tags: | + ghcr.io/${{ steps.vars.outputs.owner }}/balancer-frontend:pr-${{ github.event.number }} + ghcr.io/${{ steps.vars.outputs.owner }}/balancer-frontend:pr-${{ github.event.number }}-latest diff --git a/.github/workflows/containers-publish.yml b/.github/workflows/containers-publish.yml deleted file mode 100644 index 9cd5fcce..00000000 --- a/.github/workflows/containers-publish.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: "Containers: Publish" - -on: - push: - tags: ["v*"] - -permissions: - packages: write - -jobs: - release-containers: - name: Build and Push - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Login to ghcr.io Docker registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Compute Docker container image addresses - run: | - DOCKER_REPOSITORY="ghcr.io/${GITHUB_REPOSITORY,,}" - DOCKER_TAG="${GITHUB_REF:11}" - - echo "DOCKER_REPOSITORY=${DOCKER_REPOSITORY}" >> $GITHUB_ENV - echo "DOCKER_TAG=${DOCKER_TAG}" >> $GITHUB_ENV - - echo "Using: ${DOCKER_REPOSITORY}/*:${DOCKER_TAG}" - - # - name: 'Pull previous Docker container image: :latest' - # run: docker pull "${DOCKER_REPOSITORY}:latest" || true - - - name: "Pull previous Docker container image: frontend-static:latest" - run: docker pull "${DOCKER_REPOSITORY}/frontend-static:latest" || true - - - name: "Build Docker container image: frontend-static:latest" - run: | - docker build \ - --cache-from "${DOCKER_REPOSITORY}/frontend-static:latest" \ - --file frontend/Dockerfile.demo \ - --build-arg SERVER_NAME=localhost \ - --tag "${DOCKER_REPOSITORY}/frontend-static:latest" \ - --tag "${DOCKER_REPOSITORY}/frontend-static:${DOCKER_TAG}" \ - frontend - - name: "Push Docker container image frontend-static:latest" - run: docker push "${DOCKER_REPOSITORY}/frontend-static:latest" - - - name: "Push Docker container image frontend-static:v*" - run: docker push "${DOCKER_REPOSITORY}/frontend-static:${DOCKER_TAG}" -# -# -# - name: 'Build Docker container image: backend:latest' -# run: | -# cd backend && \ -# make && \ -# docker image tag "${DOCKER_REPOSITORY}/backend/local:latest" "${DOCKER_REPOSITORY}/backend:latest" -# -# - name: Push Docker container image backend:latest -# run: docker push "${DOCKER_REPOSITORY}/backend:latest" -# -# - name: Push Docker container image backend:v* -# run: docker push "${DOCKER_REPOSITORY}/backend:${DOCKER_TAG}" - -# - name: Push Docker container image :v*" -# run: docker push "${DOCKER_REPOSITORY}:${DOCKER_TAG}" diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml deleted file mode 100644 index 715e50a6..00000000 --- a/.github/workflows/release-prepare.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: "Release: Prepare PR" - -on: - push: - branches: [develop] - -permissions: - contents: read - pull-requests: write - -jobs: - release-prepare: - runs-on: ubuntu-latest - steps: - - uses: JarvusInnovations/infra-components@channels/github-actions/release-prepare/latest - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - release-branch: main diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml deleted file mode 100644 index 127aa129..00000000 --- a/.github/workflows/release-publish.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "Release: Publish PR" - -on: - pull_request: - branches: [main] - types: [closed] - -jobs: - release-publish: - runs-on: ubuntu-latest - steps: - - uses: JarvusInnovations/infra-components@channels/github-actions/release-publish/latest - with: - github-token: ${{ secrets.BOT_GITHUB_TOKEN }} diff --git a/.github/workflows/release-validate.yml b/.github/workflows/release-validate.yml deleted file mode 100644 index 60a2ce03..00000000 --- a/.github/workflows/release-validate.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "Release: Validate PR" - -on: - pull_request: - branches: [main] - types: [opened, edited, reopened, synchronize] - -jobs: - release-validate: - runs-on: ubuntu-latest - steps: - - uses: JarvusInnovations/infra-components@channels/github-actions/release-validate/latest - with: - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4e48c299..4cab852d 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -28,17 +28,11 @@ services: image: balancer-frontend build: context: frontend - dockerfile: Dockerfile + dockerfile: Dockerfile.prod args: - IMAGE_NAME=balancer-frontend ports: - - "3000:3000" - environment: - - CHOKIDAR_USEPOLLING=true - - VITE_API_BASE_URL=https://balancertestsite.com/ - volumes: - - "./frontend:/usr/src/app:delegated" - - "/usr/src/app/node_modules/" + - "80:80" depends_on: - backend diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod index c0648913..ad481b18 100644 --- a/frontend/Dockerfile.prod +++ b/frontend/Dockerfile.prod @@ -1,4 +1,4 @@ -FROM node:18 as builder +FROM node:23-slim AS builder WORKDIR /usr/src/app @@ -10,10 +10,10 @@ COPY . . RUN npm run build -FROM nginx:latest +FROM nginx:stable-alpine COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=builder /usr/src/app/build /usr/share/nginx/html +COPY --from=builder /usr/src/app/dist /usr/share/nginx/html EXPOSE 80 \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 05d512d6..b8dc0b1f 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -7,4 +7,14 @@ server { index index.html; try_files $uri $uri/ /index.html; } + + # Proxy API requests to the backend container + location /api/ { + proxy_pass http://backend:8000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } } \ No newline at end of file diff --git a/frontend/src/api/apiClient.ts b/frontend/src/api/apiClient.ts index d0bb35d7..674e0300 100644 --- a/frontend/src/api/apiClient.ts +++ b/frontend/src/api/apiClient.ts @@ -1,10 +1,8 @@ import axios from "axios"; import { FormValues } from "../pages/Feedback/FeedbackForm"; import { Conversation } from "../components/Header/Chat"; -const baseURL = import.meta.env.VITE_API_BASE_URL; export const api = axios.create({ - baseURL, headers: { Authorization: `JWT ${localStorage.getItem("access")}`, }, diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index cdf82274..a0fac6ce 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,19 +1,11 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -// https://vitejs.dev/config/ export default defineConfig({ - build: { - outDir: '../server/build', // Custom output directory - assetsDir: 'static', - }, plugins: [react()], server: { watch: { usePolling: true, }, - host: "0.0.0.0", - strictPort: true, - port: 3000, }, }); \ No newline at end of file diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml deleted file mode 100644 index b4da3f41..00000000 --- a/helm-chart/Chart.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: nginx-helm-chart -description: A generated Helm Chart for nginx-helm-chart from Skippbox Kompose -version: 0.0.2 -apiVersion: v2 -keywords: - - nginx-helm-chart -sources: - - https://github.com/CodeForPhilly/balancer-main -home: https://opencollective.com/code-for-philly/projects/balancer diff --git a/helm-chart/templates/frontend-static-deployment.yaml b/helm-chart/templates/frontend-static-deployment.yaml deleted file mode 100644 index ae4b72f8..00000000 --- a/helm-chart/templates/frontend-static-deployment.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - annotations: - kompose.cmd: kompose convert -c -f nginx-docker-compose.yml -o nginx-helm-chart - kompose.version: 1.31.2 (a92241f79) - creationTimestamp: null - labels: - io.kompose.service: frontend-static - name: frontend-static -spec: - replicas: 1 - selector: - matchLabels: - io.kompose.service: frontend-static - strategy: - type: Recreate - template: - metadata: - annotations: - kompose.cmd: kompose convert -c -f nginx-docker-compose.yml -o nginx-helm-chart - kompose.version: 1.31.2 (a92241f79) - creationTimestamp: null - labels: - io.kompose.network/frontend-default: "true" - io.kompose.service: frontend-static - spec: - containers: - - env: - - name: CHOKIDAR_USEPOLLING - value: "true" - - name: VITE_API_BASE_URL - value: { { .Values.VITE_API_BASE_URL } } - - image: ghcr.io/codeforphilly/balancer-main/frontend-static:latest - name: frontend-static - ports: - - containerPort: 80 - protocol: TCP - volumeMounts: - - mountPath: /etc/nginx/nginx.conf - name: nginx-conf - subPath: nginx.conf - readOnly: true - resources: {} - volumes: - - name: nginx-conf - configMap: - name: nginx-conf - items: - - key: nginx.conf - path: nginx.conf - restartPolicy: Always -status: {} diff --git a/helm-chart/templates/frontend-static-service.yaml b/helm-chart/templates/frontend-static-service.yaml deleted file mode 100644 index 26c3db19..00000000 --- a/helm-chart/templates/frontend-static-service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - annotations: - kompose.cmd: kompose convert -c -f nginx-docker-compose.yml -o nginx-helm-chart - kompose.version: 1.31.2 (a92241f79) - creationTimestamp: null - labels: - io.kompose.service: frontend-static - name: frontend-static -spec: - ports: - - name: "http" - port: 80 - protocol: TCP - selector: - io.kompose.service: frontend-static -status: - loadBalancer: {} diff --git a/helm-chart/templates/nginx-configmap.yaml b/helm-chart/templates/nginx-configmap.yaml deleted file mode 100644 index ecbf1e6b..00000000 --- a/helm-chart/templates/nginx-configmap.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: nginx-conf -# https://stackoverflow.com/questions/64178370/custom-nginx-conf-from-configmap-in-kubernetes -data: - nginx.conf: | - user nginx; - worker_processes 1; - events { - worker_connections 1024; - } - http { - include /etc/nginx/mime.types; - error_log /var/log/nginx/error_log; - access_log /var/log/nginx/access_log; - server { - listen 80; - listen [::]:80; - server_name {{ .Values.nginx.serverName }}; - - location /access_log { - alias /var/log/nginx/access_log; - } - location /error_log { - alias /var/log/nginx/error_log; - } - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - } - - #error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - # - #error_page 500 502 503 504 /50x.html; - #location = /50x.html { - # root /usr/share/nginx/html; - - #} - } - } diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml deleted file mode 100644 index 0aed6b2e..00000000 --- a/helm-chart/values.yaml +++ /dev/null @@ -1,18 +0,0 @@ -nginx: - serverName: "localhost" - -VITE_API_BASE_URL: https://devnull-as-a-service.com/dev/null - -ingress: - enabled: false - className: "" - annotations: - {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] diff --git a/k8s/Chart.yaml b/k8s/Chart.yaml new file mode 100644 index 00000000..c432b144 --- /dev/null +++ b/k8s/Chart.yaml @@ -0,0 +1,7 @@ +name: balancer +description: The helm chart for the balancer project +version: 0.1.0 +apiVersion: v2 +sources: + - https://github.com/CodeForPhilly/balancer-main +home: https://opencollective.com/code-for-philly/projects/balancer diff --git a/helm-chart/README.md b/k8s/README.md similarity index 100% rename from helm-chart/README.md rename to k8s/README.md diff --git a/k8s/templates/_helpers.tpl b/k8s/templates/_helpers.tpl new file mode 100644 index 00000000..9c8327da --- /dev/null +++ b/k8s/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "balancer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "balancer.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "balancer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "balancer.labels" -}} +helm.sh/chart: {{ include "balancer.chart" . }} +{{ include "balancer.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "balancer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "balancer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/k8s/templates/backend/deployment.yaml b/k8s/templates/backend/deployment.yaml new file mode 100644 index 00000000..51adadb7 --- /dev/null +++ b/k8s/templates/backend/deployment.yaml @@ -0,0 +1,91 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-backend + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + replicas: {{ .Values.backend.replicaCount | default 1 }} + selector: + matchLabels: + {{- include "balancer.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: backend + template: + metadata: + labels: + {{- include "balancer.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: backend + spec: + containers: + - name: backend + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + env: + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-balancer-secrets + key: secret-key + - name: DEBUG + value: {{ .Values.backend.config.debug | default "1" | quote }} + - name: DJANGO_ALLOWED_HOSTS + value: {{ .Values.backend.config.allowedHosts | default "*" | quote }} + - name: DATABASE + value: "postgres" + - name: EMAIL_HOST_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-balancer-secrets + key: email-password + - name: EMAIL_HOST_USER + value: {{ .Values.email.hostUser | default "balancer-noreply@codeforphilly.org" | quote }} + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-balancer-secrets + key: openai-api-key + - name: SQL_USER + value: {{ .Values.database.user | default "balancer" | quote }} + - name: SQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-balancer-secrets + key: database-password + - name: SQL_ENGINE + value: "django.db.backends.postgresql" + - name: SQL_HOST + value: {{ if .Values.database.internal }}{{ printf "%s-db.%s.svc.cluster.local" .Release.Name .Release.Namespace | quote }}{{ else }}{{ .Values.database.host | quote }}{{ end }} + - name: SQL_PORT + value: {{ .Values.database.port | default "5432" | quote }} + - name: SQL_DATABASE + value: {{ .Values.database.name | default "balancer_dev" | quote }} + resources: + requests: + cpu: {{ .Values.backend.resources.requests.cpu | default "500m" }} + memory: {{ .Values.backend.resources.requests.memory | default "1Gi" }} + limits: + cpu: {{ .Values.backend.resources.limits.cpu | default "1024m" }} + memory: {{ .Values.backend.resources.limits.memory | default "3Gi" }} + readinessProbe: + httpGet: + path: /health/ + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health/ + port: http + initialDelaySeconds: 30 + periodSeconds: 15 + startupProbe: + httpGet: + path: /health/ + port: http + failureThreshold: 30 + periodSeconds: 10 \ No newline at end of file diff --git a/k8s/templates/backend/sealed-secrets.yaml b/k8s/templates/backend/sealed-secrets.yaml new file mode 100644 index 00000000..6c419863 --- /dev/null +++ b/k8s/templates/backend/sealed-secrets.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: {{ .Release.Name }}-backend-secrets + namespace: {{ .Release.Namespace }} + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + encryptedData: + database-password: AgBeM+d8ksKjIfgQDe1dQddT2h7hI7MosIh7o6ctBEGlegMkTp082XApZh4pNSXqtEMCbQCWBlV875dLCuOmXpguYZZ0Ldjj/vNgKR+gK8lmRQGc08DaHNMLychraYt6LKkAq/5PeLHcEQ/hyPz1jN+Mk1Omqiqdz3GvNi4TUaOdmsf8aXygXJlZZwCp/03akaKkuckK+rGd2n3lPzdC/03KkbaSKj4K5RrlTqOIYgwLzibM0NdfEWDhnmzf7ruz+WBE4u1iILFfFKGD7EbpXGbWLn7FsGNxOY8Qre15vG/0nc62x1nmN99A56Y2HcM3ezjOVfSepPWQBsiUHX94Lcdzw5bqJOVr/ZHvA2Q30U8Gj/v270+yOBBAX91pwoAwm+aC5rkjCF+kZYR3LXHne07Mjicta+/xq0Cgu4BhSjGCbg7ayxobBayClb7Fv7Gp8IkMgO1zhwyaqCHslJVydxh15Q4KE8pjtJ81cra4/kbinLj90q4iw+2BRgm/aqS5olaLrivZEV107a5H58SY+W9AvVFvPCU3pi4x0bFlJb+PCkRrnzbtEQ+FblRnIvK/z33QjnrrL+Svhvt54KKN8w5VJgV2ruqsLYyGAO2B/V6cyISpogx4fOrJAMktdVsCeB7rPMWudoNw03wKbQcau/tnHW8YFdTZE6YChS/rUy7XYt+ir2L4JPZX1EG0KxTCCI1JeEzWkSmnlW3M + email-password: AgB7HPKbeHwmvwBkbPawZel2epsKJ9n9gcjUGRvkn+dEgl8SOphoSy887RzmPxcUO8NH8A7m7gTHPHv96qDbpe4ocNEKPxVuswheVi/CYGfz3arJuXUyQamI0S9P67Wx7h2hJRNd9JibbQYxnMCwvt4l2h1rVUuOq8LgHR743bTG4eVX2nxT0gRrxry11MYyvFfGq7LAlZDnRrzG6DUpSbH6tPhqm1W7ixXwD96Z+7VBm/E7Xa9ZkkKv0SxofPL3JdWYrUyiaNr+XiiNcxt+Xwixjl3RQ77WLBsfHsOdDD6/oBUOnek8cKXyM+oA4nKVNoGj9MqlY62TrpIq08UEGYcE3tmRDX8rKGJW9ymF9pYtwLSeHtJIEYrWkd+ckjtGS1J4ad+/K3Ax3lVwhztJjaaIquZJ+lOIWO3qRQZ2AVpZhs+AmzhO8NEBO2f9n0NTqN9pI5jEA+DhTb5K4yzUQwhwBZwoEXyAftlfsrtyjpkvXDXxx//th7XRAVVu1cKeW7hjiiX0f8kwXAecZvp6mrl53+GnleaPptC4mICjiE/8ObMFDybvxgX2sGZjhAxGgRutNpkwRumly7gO4gb1rPzDhmdOmG0RGVX7vX+ml9eJXwpYASic45yHN1L5W5j55D0diAOMtYDC0EuZqItdMTpmTNYDGricbqL8EbbkSnXVsL8iL9XcgakG1zYUxDFYybQDxRMeOhPdLvWeXM9hrZGv + openai-api-key: AgBesie023rZM4uDmn8keGnRGMAEpOp8/s4sbrf/8HWuMRIAL8lw0UPvDDj2qYk8u+EvSAT/N2l0bKA7/ijd7caknfeMvynU8t6J6YDEsuCfKDA02SYSUCl91tVBJJ+Ms0kDSc16z6bf/bXjrJo2fe+3add+Xtodg4rUrrX6pUKZlyF1AqmAVg2REKMqrLm0NlRZCso1KLcZvtHcgLbpJ1OtN8qGFcgUGAl1k1QxpV6/WfYSFp34TsGZXxisX/nFSf2BpIugqEtHZwXAvSyPub7rz0f0YaJRgrQn+qjHJ20TII2nv7TqY2HEGCHwa1riu1HNhDWfFCaHY+om1psVUe5C644qMeUEQva6T2cdS39Ve2F7fcEGn+0mXFno2syM+h2C9RAWs0mitEv1Xh59cQh12SRsqKH6j6fV/MWDnr9vS9Qy/tVKmGPWqZk+KEaRn5pMtqyHUFU7hnJvKGvhg0oXmlDZYbG88RjsXGEJo1N98WpYoUefDnaIglGCDBG6oMTgaRMzCz4WzQQxoOoUGDlhrqFQ/s2CCcA2yqFwRgxh2i4/fdgRE0SCQFqvI2dt98vpeTxe0OKNQjKBlQNbG0xNdm/JYsGUJwN3JRIWakANbwK1DgHQjRSG9pf9N/HLqSPEhdrLWsqSNCygxHIWI09PTjrxm9B/nXRPyE/q/8y6vfS2x5/TQSvZxgJfi81YMH7sgZZ8C79SbNMoGnQPKT8ZFgXlGSqXbfDqmOTX+9FFnx3hHNrUS26sTRKlSit3h8uvKEfEnDZHTg== + secret-key: AgCzCdAb7Z9ROU6aow5xLi1uOlt/mEtjust3HnGZLKv4eVPQOG6cSpiYaWYS1cWMOTjZzUw6POOuHkGAmiikI8d6GG2Dj9HF2zTYt3P/UKoIig1vr6PjxxMM7KFrRopDASyKDZ+2xHS1s/F8T49y2J49nZzFpRgcTW8cH731FRW229dAL9AlehxWq59tGezyBvnm6v085H0brx+YWEJ855tFMAjEUF+qvWloIKJeGQXM7QXaIWAUqHc7u3oIFyNPrQNhOC4GhpKTl96PJ1Rwm3atPMBuz0NmfKM9dWXFfl8KWka2ctbwxz+jmNGqFh0He3RqREXcgJdjiCJ7EAUB2ecZuKXwRusDWVAweOLWJQb5MMZQbEdNOPhk48bdx6QpKYTHLxDK6l2nLTYtorTAlIzOQXgerjHsRwA/eabS2jzejjrkCKFtXuLlk4Gr0mTziFzXA2PrEt+rJrUYmJ4W1Jx2aaWXC2D7KR+Jm7JELMmWiUAlehevmiZOx46w5bRb7+cvTqWSDo+GuqSe0W2ZOI8ME//ba8a5qXWJmxQoQtdOzE2u3g0p7DkUORUneO8bfEQri8nHQLjJiBvAgiebnGEfyFtSrtutCj3XpoB1WUenOVSooKWuMXbMzCaAYvd114f5neBXs0n8Gy/r0wq6ayjvsW5QfFRazk9Pl813rFI5a4ZCLf4FSdBUXOHokJz91GPYeoU= + template: + metadata: + name: {{ .Release.Name }}-backend-secrets + namespace: {{ .Release.Namespace }} + labels: + {{- include "balancer.labels" . | nindent 8 }} + app.kubernetes.io/component: backend diff --git a/k8s/templates/backend/service.yaml b/k8s/templates/backend/service.yaml new file mode 100644 index 00000000..db1dd640 --- /dev/null +++ b/k8s/templates/backend/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-backend + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + type: {{ .Values.backend.service.type | default "ClusterIP" }} + ports: + - port: {{ .Values.backend.service.port | default 8000 }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "balancer.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: backend \ No newline at end of file diff --git a/k8s/templates/db/_helpers.tpl b/k8s/templates/db/_helpers.tpl new file mode 100644 index 00000000..591444b3 --- /dev/null +++ b/k8s/templates/db/_helpers.tpl @@ -0,0 +1,29 @@ +{{/* +Database name +*/}} +{{- define "balancer.db.name" -}} +{{- .Values.database.name | default "balancer_dev" }} +{{- end }} + +{{/* +Database user +*/}} +{{- define "balancer.db.user" -}} +{{- .Values.database.user | default "balancer" }} +{{- end }} + +{{/* +Database labels +*/}} +{{- define "balancer.db.labels" -}} +{{- include "balancer.labels" . | nindent 0 }} +app.kubernetes.io/component: database +{{- end }} + +{{/* +Database selector labels +*/}} +{{- define "balancer.db.selectorLabels" -}} +{{- include "balancer.selectorLabels" . | nindent 0 }} +app.kubernetes.io/component: database +{{- end }} \ No newline at end of file diff --git a/k8s/templates/db/deployment.yaml b/k8s/templates/db/deployment.yaml new file mode 100644 index 00000000..b2e34d7d --- /dev/null +++ b/k8s/templates/db/deployment.yaml @@ -0,0 +1,69 @@ +{{- if .Values.database.internal }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-db + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + replicas: 1 + selector: + matchLabels: + {{- include "balancer.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: database + template: + metadata: + labels: + {{- include "balancer.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: "{{ .Values.database.image.repository }}:{{ .Values.database.image.tag }}" + imagePullPolicy: {{ .Values.database.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: postgres + containerPort: 5432 + protocol: TCP + env: + - name: POSTGRES_USER + value: {{ .Values.database.user | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-balancer-secrets + key: database-password + - name: POSTGRES_DB + value: {{ .Values.database.name | quote }} + resources: + requests: + cpu: {{ .Values.database.resources.requests.cpu | default "100m" }} + memory: {{ .Values.database.resources.requests.memory | default "256Mi" }} + limits: + cpu: {{ .Values.database.resources.limits.cpu | default "500m" }} + memory: {{ .Values.database.resources.limits.memory | default "512Mi" }} + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: + - pg_isready + - -U + - {{ .Values.database.user }} + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: + - pg_isready + - -U + - {{ .Values.database.user }} + initialDelaySeconds: 30 + periodSeconds: 15 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-db-pvc +{{- end }} \ No newline at end of file diff --git a/k8s/templates/db/pvc.yaml b/k8s/templates/db/pvc.yaml new file mode 100644 index 00000000..7cf92095 --- /dev/null +++ b/k8s/templates/db/pvc.yaml @@ -0,0 +1,18 @@ +{{- if .Values.database.internal }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-db-pvc + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + accessModes: + - {{ .Values.database.persistence.accessMode | default "ReadWriteOnce" }} + resources: + requests: + storage: {{ .Values.database.persistence.size | default "1Gi" }} + {{- if .Values.database.persistence.storageClass }} + storageClassName: {{ .Values.database.persistence.storageClass }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/templates/db/service.yaml b/k8s/templates/db/service.yaml new file mode 100644 index 00000000..24d22b44 --- /dev/null +++ b/k8s/templates/db/service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.database.internal }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-db + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: postgres + protocol: TCP + name: postgres + selector: + {{- include "balancer.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: database +{{- end }} \ No newline at end of file diff --git a/k8s/templates/frontend/deployment.yaml b/k8s/templates/frontend/deployment.yaml new file mode 100644 index 00000000..c1d7bc5d --- /dev/null +++ b/k8s/templates/frontend/deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-frontend + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + replicas: {{ .Values.frontend.replicaCount | default 1 }} + selector: + matchLabels: + {{- include "balancer.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: frontend + template: + metadata: + labels: + {{- include "balancer.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: frontend + spec: + containers: + - name: frontend + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + resources: + requests: + cpu: {{ .Values.frontend.resources.requests.cpu | default "100m" }} + memory: {{ .Values.frontend.resources.requests.memory | default "256Mi" }} + limits: + cpu: {{ .Values.frontend.resources.limits.cpu | default "500m" }} + memory: {{ .Values.frontend.resources.limits.memory | default "512Mi" }} \ No newline at end of file diff --git a/k8s/templates/frontend/service.yaml b/k8s/templates/frontend/service.yaml new file mode 100644 index 00000000..ff5baf5f --- /dev/null +++ b/k8s/templates/frontend/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-frontend + labels: + {{- include "balancer.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + type: {{ .Values.frontend.service.type | default "ClusterIP" }} + ports: + - port: {{ .Values.frontend.service.port | default 3000 }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "balancer.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: frontend \ No newline at end of file diff --git a/k8s/values.yaml b/k8s/values.yaml new file mode 100644 index 00000000..629e72ec --- /dev/null +++ b/k8s/values.yaml @@ -0,0 +1,71 @@ +nameOverride: "" +fullnameOverride: "" + +backend: + replicaCount: 1 + image: + repository: balancer-backend + tag: latest + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 8000 + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1024m + memory: 3Gi + config: + debug: "1" + allowedHosts: "*" + +frontend: + replicaCount: 1 + image: + repository: balancer-frontend + tag: latest + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 3000 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + secrets: + apiKey: "" # Example encrypted value + +# Database configuration +database: + # Set to true to use the internal PostgreSQL database + # Set to false to use an external database + internal: true + # External database configuration (used when internal: false) + host: "your-external-db-host.com" + port: "5432" + # Common database configuration (used for both internal and external) + image: + repository: postgres + tag: "14" + pullPolicy: IfNotPresent + user: balancer + name: balancer_dev + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + persistence: + size: 1Gi + accessMode: ReadWriteOnce + storageClass: "" # Use default storage class if not specified + +email: + hostUser: balancer-noreply@codeforphilly.org \ No newline at end of file diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 00000000..0f1e87d1 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,15 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +*.db +*.sqlite3 +.env +.git +.gitignore +Dockerfile +.dockerignore +tests +docs +node_modules +*.log