From 2781ab1077db33583c2605756407359208a1bc30 Mon Sep 17 00:00:00 2001 From: PouyaMohseni Date: Tue, 30 Dec 2025 21:19:32 -0500 Subject: [PATCH 01/21] refactor: refine verification status badge rendering --- web-app/django/VIM/templates/instruments/detail.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/detail.html b/web-app/django/VIM/templates/instruments/detail.html index e2e77c03..e928aa81 100644 --- a/web-app/django/VIM/templates/instruments/detail.html +++ b/web-app/django/VIM/templates/instruments/detail.html @@ -161,7 +161,7 @@

{{ active_instrument_label.name }}

Unverified {% elif names.label.verification_status == 'rejected' %} Rejected - {% else %} + {% elif names.label.verification_status == 'under_review' %} Under Review {% endif %} @@ -242,7 +242,7 @@

{{ active_instrument_label.name }}

Unverified {% elif names.label.verification_status == 'rejected' %} Rejected - {% else %} + {% elif names.label.verification_status == 'under_review' %} Under Review {% endif %} From a57eb8a8cbbe51443133aea314a0e9b78ddaba17 Mon Sep 17 00:00:00 2001 From: PouyaMohseni Date: Sun, 5 Apr 2026 14:57:54 -0400 Subject: [PATCH 02/21] fix: prevent the source from overflow - increase the lenght of the source to 255 (max in CharField) - truncate source when displayed - add charecter count ref #455 --- .../0014_alter_instrument_source_and_more.py | 29 ++++++++++++++++++ .../VIM/apps/instruments/models/instrument.py | 2 +- .../instruments/models/instrument_name.py | 2 +- .../instruments/views/create_instrument.py | 6 ++-- .../VIM/templates/instruments/create.html | 8 +++-- .../VIM/templates/instruments/detail.html | 4 +-- .../helpers/CreateInstrumentManager.ts | 30 +++++++++++++++++-- .../helpers/CreateInstrumentValidator.ts | 18 +++++++++-- .../src/instruments/helpers/NameRowManager.ts | 28 +++++++++++++++-- .../src/instruments/helpers/NameValidator.ts | 12 ++++++-- 10 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 web-app/django/VIM/apps/instruments/migrations/0014_alter_instrument_source_and_more.py diff --git a/web-app/django/VIM/apps/instruments/migrations/0014_alter_instrument_source_and_more.py b/web-app/django/VIM/apps/instruments/migrations/0014_alter_instrument_source_and_more.py new file mode 100644 index 00000000..78d5781e --- /dev/null +++ b/web-app/django/VIM/apps/instruments/migrations/0014_alter_instrument_source_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.5 on 2026-04-05 18:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("instruments", "0013_alter_avresource_format_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="instrument", + name="source", + field=models.CharField( + blank=True, + help_text="Source/reference for this instrument entry", + max_length=255, + ), + ), + migrations.AlterField( + model_name="instrumentname", + name="source_name", + field=models.CharField( + help_text="Who or what called the instrument this?", max_length=255 + ), + ), + ] diff --git a/web-app/django/VIM/apps/instruments/models/instrument.py b/web-app/django/VIM/apps/instruments/models/instrument.py index 7c8e432f..377895df 100644 --- a/web-app/django/VIM/apps/instruments/models/instrument.py +++ b/web-app/django/VIM/apps/instruments/models/instrument.py @@ -45,7 +45,7 @@ class Instrument(models.Model): help_text="User who created this instrument (null for Wikidata imports)", ) source = models.CharField( - max_length=200, + max_length=255, blank=True, help_text="Source/reference for this instrument entry", ) diff --git a/web-app/django/VIM/apps/instruments/models/instrument_name.py b/web-app/django/VIM/apps/instruments/models/instrument_name.py index b31af750..cef40451 100644 --- a/web-app/django/VIM/apps/instruments/models/instrument_name.py +++ b/web-app/django/VIM/apps/instruments/models/instrument_name.py @@ -6,7 +6,7 @@ class InstrumentName(models.Model): language = models.ForeignKey("Language", on_delete=models.PROTECT) name = models.CharField(max_length=100, blank=False) source_name = models.CharField( - max_length=50, blank=False, help_text="Who or what called the instrument this?" + max_length=255, blank=False, help_text="Who or what called the instrument this?" ) # Stand-in for source data; format TBD verification_status = models.CharField( max_length=50, diff --git a/web-app/django/VIM/apps/instruments/views/create_instrument.py b/web-app/django/VIM/apps/instruments/views/create_instrument.py index d53be56a..f1813501 100644 --- a/web-app/django/VIM/apps/instruments/views/create_instrument.py +++ b/web-app/django/VIM/apps/instruments/views/create_instrument.py @@ -110,11 +110,11 @@ def create_instrument(request: HttpRequest) -> JsonResponse: ) # Validate instrument source length - if len(instrument_source) > 200: + if len(instrument_source) > 255: raise ValidationException( ErrorCode.FIELD_TOO_LONG, - "Instrument source must be 200 characters or less", - details={"field": "instrument_source", "max_length": 200}, + "Instrument source must be 255 characters or less", + details={"field": "instrument_source", "max_length": 255}, ) if not validate_hbs_classification(hbs_class): diff --git a/web-app/django/VIM/templates/instruments/create.html b/web-app/django/VIM/templates/instruments/create.html index e070ae3b..a90fa8fc 100644 --- a/web-app/django/VIM/templates/instruments/create.html +++ b/web-app/django/VIM/templates/instruments/create.html @@ -38,12 +38,16 @@
Instrument Information
id="instrumentSource" name="instrument_source" placeholder="Enter instrument source" + maxlength="255" required />
The primary source or reference for this instrument's existence.
-
-
+
+
+
+ 0 / 255 +
From 511d42eb4ca46e681f34687a6f6b4dd1f6744189 Mon Sep 17 00:00:00 2001 From: PouyaMohseni Date: Thu, 23 Apr 2026 23:25:22 -0400 Subject: [PATCH 07/21] feat: link HBS flowcharts to Create Instrument page resolves #526 --- .../VIM/templates/instruments/create.html | 2 +- .../instruments/includes/addName.html | 2 +- .../src/instruments/helpers/AddNameManager.ts | 21 ++++++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/create.html b/web-app/django/VIM/templates/instruments/create.html index 5f3a47ab..e5302573 100644 --- a/web-app/django/VIM/templates/instruments/create.html +++ b/web-app/django/VIM/templates/instruments/create.html @@ -61,7 +61,7 @@
Instrument Information
required /> - +
diff --git a/web-app/frontend/src/instruments/helpers/AddNameManager.ts b/web-app/frontend/src/instruments/helpers/AddNameManager.ts index 388e1f18..7deb5f32 100644 --- a/web-app/frontend/src/instruments/helpers/AddNameManager.ts +++ b/web-app/frontend/src/instruments/helpers/AddNameManager.ts @@ -7,6 +7,7 @@ import { Modal } from 'bootstrap'; export class AddNameManager { private nameRowManager: NameRowManager; private nameValidator: NameValidator; + private isPublishing = false; constructor(nameRowManager: NameRowManager, nameValidator: NameValidator) { this.nameRowManager = nameRowManager; @@ -38,7 +39,7 @@ export class AddNameManager { document .getElementById('confirmPublishBtn') ?.addEventListener('click', () => { - this.submitNames(); + void this.submitNames(); }); } @@ -158,6 +159,18 @@ export class AddNameManager { * Submits name entries to the backend API */ private async submitNames(): Promise { + if (this.isPublishing) return; + this.isPublishing = true; + + const confirmBtn = document.getElementById( + 'confirmPublishBtn', + ) as HTMLButtonElement | null; + const previousLabel = confirmBtn?.textContent ?? ''; + if (confirmBtn) { + confirmBtn.disabled = true; + confirmBtn.textContent = 'Saving…'; + } + const umilId = document .getElementById('instrumentUmilIdInModal') ?.textContent?.trim(); @@ -193,6 +206,12 @@ export class AddNameManager { } } catch (error) { alert('An error occurred while publishing: ' + (error as Error).message); + } finally { + this.isPublishing = false; + if (confirmBtn) { + confirmBtn.disabled = false; + confirmBtn.textContent = previousLabel || 'Confirm'; + } } } From a3ff3865c6e813b17b5ea77e616aed475ae79c70 Mon Sep 17 00:00:00 2001 From: PouyaMohseni Date: Fri, 1 May 2026 07:16:35 -0400 Subject: [PATCH 08/21] fix: Prevent overflow on md screens --- .../frontend/src/instruments/helpers/NameRowManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web-app/frontend/src/instruments/helpers/NameRowManager.ts b/web-app/frontend/src/instruments/helpers/NameRowManager.ts index 64849682..dde1a1d1 100644 --- a/web-app/frontend/src/instruments/helpers/NameRowManager.ts +++ b/web-app/frontend/src/instruments/helpers/NameRowManager.ts @@ -57,7 +57,7 @@ export class NameRowManager { const maxSourceLen = 255; row.innerHTML = ` -
+
@@ -67,14 +67,14 @@ export class NameRowManager {
-
+
-
+
@@ -84,7 +84,7 @@ export class NameRowManager { 0 / ${maxSourceLen}
-
+
From 03a3ede41911eb6ccecf149a6b4c9e0c15987caf Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 11:16:27 -0400 Subject: [PATCH 09/21] Feat: Add postgres manifests --- k8s/postgres/pvc.yaml | 12 ++++++ k8s/postgres/secret.yaml | 9 +++++ k8s/postgres/service.yaml | 12 ++++++ k8s/postgres/statefulset.yaml | 74 +++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 k8s/postgres/pvc.yaml create mode 100644 k8s/postgres/secret.yaml create mode 100644 k8s/postgres/service.yaml create mode 100644 k8s/postgres/statefulset.yaml diff --git a/k8s/postgres/pvc.yaml b/k8s/postgres/pvc.yaml new file mode 100644 index 00000000..a3faef9a --- /dev/null +++ b/k8s/postgres/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-umil-pvc + namespace: postgres +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/k8s/postgres/secret.yaml b/k8s/postgres/secret.yaml new file mode 100644 index 00000000..cbedd545 --- /dev/null +++ b/k8s/postgres/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-umil-secret + namespace: postgres +type: Opaque +stringData: + POSTGRES_PASSWORD: "" + diff --git a/k8s/postgres/service.yaml b/k8s/postgres/service.yaml new file mode 100644 index 00000000..cbfe7bd7 --- /dev/null +++ b/k8s/postgres/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-umil + namespace: postgres +spec: + selector: + app: postgres-umil + ports: + - port: 5432 + targetPort: 5432 + type: ClusterIP \ No newline at end of file diff --git a/k8s/postgres/statefulset.yaml b/k8s/postgres/statefulset.yaml new file mode 100644 index 00000000..49c5cd54 --- /dev/null +++ b/k8s/postgres/statefulset.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres-umil + namespace: postgres +spec: + serviceName: postgres-umil + replicas: 1 + selector: + matchLabels: + app: postgres-umil + template: + metadata: + labels: + app: postgres-umil + spec: + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: database + nodeSelector: + workload: database + containers: + - name: postgres + image: postgres:15.4 + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: virtual_instrument_museum + - name: POSTGRES_USER + value: vim_admin + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-umil-secret + key: POSTGRES_PASSWORD + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-umil-pvc From 1c5bc9beee257cf9ead1cc018173f3acac203020 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 11:40:18 -0400 Subject: [PATCH 10/21] Feat: Add solr k8s manifests --- k8s/solr/configmap.yaml | 194 ++++++++++++++++++++++++++++++++++++++ k8s/solr/pvc.yaml | 12 +++ k8s/solr/service.yaml | 12 +++ k8s/solr/statefulset.yaml | 87 +++++++++++++++++ 4 files changed, 305 insertions(+) create mode 100644 k8s/solr/configmap.yaml create mode 100644 k8s/solr/pvc.yaml create mode 100644 k8s/solr/service.yaml create mode 100644 k8s/solr/statefulset.yaml diff --git a/k8s/solr/configmap.yaml b/k8s/solr/configmap.yaml new file mode 100644 index 00000000..fb267efd --- /dev/null +++ b/k8s/solr/configmap.yaml @@ -0,0 +1,194 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: solr-core-config + namespace: umil +data: + core.properties: | + name=virtual-instrument-museum + schema.xml: | + + + + + sid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + solrconfig.xml: | + + + + + + + 9.4.2 + ${solr.data.dir:/var/solr} + + + + + true + + + + + ${solr.ulog.dir:} + ${solr.ulog.numVersionBuckets:65536} + + + ${solr.autoCommit.maxTime:15000} + false + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + 1024 + + + + true + 20 + 200 + false + 2 + + + + + + + + + + explicit + 10 + text + + + + + + + explicit + json + true + text + + + + + text + + + + \ No newline at end of file diff --git a/k8s/solr/pvc.yaml b/k8s/solr/pvc.yaml new file mode 100644 index 00000000..c3035c6e --- /dev/null +++ b/k8s/solr/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: solr-data + namespace: umil +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/k8s/solr/service.yaml b/k8s/solr/service.yaml new file mode 100644 index 00000000..e000cf4b --- /dev/null +++ b/k8s/solr/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: solr + namespace: umil +spec: + selector: + app: umil-solr + ports: + - port: 8983 + targetPort: 8983 + type: ClusterIP \ No newline at end of file diff --git a/k8s/solr/statefulset.yaml b/k8s/solr/statefulset.yaml new file mode 100644 index 00000000..cb85f95c --- /dev/null +++ b/k8s/solr/statefulset.yaml @@ -0,0 +1,87 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: umil-solr + namespace: umil +spec: + serviceName: solr + replicas: 1 + selector: + matchLabels: + app: umil-solr + template: + metadata: + labels: + app: umil-solr + spec: + securityContext: + fsGroup: 8983 + initContainers: + - name: setup-core + image: busybox + securityContext: + runAsUser: 0 + command: + - /bin/sh + - -c + - | + CORE_DIR=/var/solr/data/virtual-instrument-museum + if [ ! -d "$CORE_DIR" ]; then + echo "Initializing Solr core..." + mkdir -p "$CORE_DIR/conf" + cp /tmp/core-config/core.properties "$CORE_DIR/" + cp /tmp/core-config/schema.xml "$CORE_DIR/conf/" + cp /tmp/core-config/solrconfig.xml "$CORE_DIR/conf/" + chown -R 8983:8983 /var/solr/data + echo "Solr core initialized at $CORE_DIR" + else + echo "Solr core already exists, skipping setup" + fi + volumeMounts: + - name: solr-data + mountPath: /var/solr/data + - name: core-config + mountPath: /tmp/core-config + + containers: + - name: solr + image: solr:9.2.1 + ports: + - containerPort: 8983 + env: + - name: SOLR_SECURITY_MANAGER_ENABLED + value: "false" + volumeMounts: + - name: solr-data + mountPath: /var/solr/data + readinessProbe: + httpGet: + path: /solr/virtual-instrument-museum/admin/ping + port: 8983 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /solr/virtual-instrument-museum/admin/ping + port: 8983 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1" + + volumes: + - name: solr-data + persistentVolumeClaim: + claimName: solr-data + - name: core-config + configMap: + name: solr-core-config From 232795d40acce642961167c5bb9f158399615aef Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 12:22:17 -0400 Subject: [PATCH 11/21] Feat: Add app and nginx k8s manifests --- .github/workflows/build-push.yml | 54 ++++++++++ .github/workflows/ci_build_deployment.yml | 35 ------ k8s/app/configmap.yaml | 15 +++ k8s/app/deployment.yaml | 123 ++++++++++++++++++++++ k8s/app/ingress.yaml | 17 +++ k8s/app/postgres-alias.yaml | 8 ++ k8s/app/pvc-media.yaml | 12 +++ k8s/app/secret.yaml | 10 ++ k8s/app/service-app.yaml | 12 +++ k8s/app/service-nginx.yaml | 12 +++ 10 files changed, 263 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/build-push.yml delete mode 100644 .github/workflows/ci_build_deployment.yml create mode 100644 k8s/app/configmap.yaml create mode 100644 k8s/app/deployment.yaml create mode 100644 k8s/app/ingress.yaml create mode 100644 k8s/app/postgres-alias.yaml create mode 100644 k8s/app/pvc-media.yaml create mode 100644 k8s/app/secret.yaml create mode 100644 k8s/app/service-app.yaml create mode 100644 k8s/app/service-nginx.yaml diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 00000000..2c0800fa --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,54 @@ +name: Build, Push + +on: + push: + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Sanitize branch name + id: branch + run: echo "name=$(echo '${{ github.ref_name }}' | sed 's/[^a-zA-Z0-9._-]/-/g')" >> $GITHUB_OUTPUT + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push app + uses: docker/build-push-action@v5 + with: + context: . + file: ./web-app/Dockerfile + target: prod + push: true + tags: | + ghcr.io/ddmal/umil-app:${{ steps.branch.outputs.name }}-latest + ghcr.io/ddmal/umil-app:${{ github.sha }} + cache-from: type=gha,scope=app + cache-to: type=gha,mode=max,scope=app + + - name: Build and push nginx + uses: docker/build-push-action@v5 + with: + context: . + file: ./nginx/Dockerfile.prod + push: true + tags: | + ghcr.io/ddmal/umil-nginx:${{ steps.branch.outputs.name }}-latest + ghcr.io/ddmal/umil-nginx:${{ github.sha }} + cache-from: type=gha,scope=nginx + cache-to: type=gha,mode=max,scope=nginx diff --git a/.github/workflows/ci_build_deployment.yml b/.github/workflows/ci_build_deployment.yml deleted file mode 100644 index 45937cfd..00000000 --- a/.github/workflows/ci_build_deployment.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: CI - Test Docker build for deployment - -on: - push: - branches: [develop, main] - pull_request: - branches: [develop] - workflow_dispatch: - -jobs: - ci-build-deployment: - runs-on: ubuntu-latest - env: - HOST_NAME: umil.linkedmusic.ca - DJANGO_SECRET_KEY: ${{ secrets.CI_DJANGO_SECRET_KEY }} - POSTGRES_DB: virtual_instrument_museum - POSTGRES_USER: ${{ secrets.CI_POSTGRES_USER }} - POSTGRES_PASSWORD: ${{ secrets.CI_POSTGRES_PASSWORD }} - - steps: - - name: Checkout the current repo - uses: actions/checkout@v5 - - - name: Set up Docker Compose - uses: docker/setup-compose-action@v1 - with: - version: latest - - - name: Build UMIL with Production compose file - run: | - docker compose -f docker-compose-deployment.yml build - - - name: List built Docker images - run: | - docker images diff --git a/k8s/app/configmap.yaml b/k8s/app/configmap.yaml new file mode 100644 index 00000000..403d6917 --- /dev/null +++ b/k8s/app/configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: umil-config + namespace: umil +data: + MODE: "prod" + HOST_NAME: "umil.linkedmusic.ca" + EMAIL_HOST: "email-smtp.us-west-2.amazonaws.com" + EMAIL_PORT: "587" + EMAIL_HOST_USER: "AKIASGTLL5NYVV7JG65A" + DEFAULT_FROM_EMAIL: "UMIL " + POSTGRES_USER: "vim_admin" + POSTGRES_DB: "virtual_instrument_museum" + diff --git a/k8s/app/deployment.yaml b/k8s/app/deployment.yaml new file mode 100644 index 00000000..cdda3e29 --- /dev/null +++ b/k8s/app/deployment.yaml @@ -0,0 +1,123 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: umil-app + namespace: umil +spec: + replicas: 1 + selector: + matchLabels: + app: umil-app + template: + metadata: + labels: + app: umil-app + spec: + imagePullSecrets: + - name: ghcr-pull-secret + initContainers: + - name: wait-for-db + image: busybox + command: + - /bin/sh + - -c + - until nc -z vim-db 5432; do echo "Waiting for postgres..."; sleep 2; done + + - name: migrate + image: ghcr.io/ddmal/umil-app:develop-latest + workingDir: /virtual-instrument-museum/vim-app + command: ["python", "manage.py", "migrate", "--noinput"] + envFrom: + - configMapRef: + name: umil-config + - secretRef: + name: umil-secret + + - name: collectstatic + image: ghcr.io/ddmal/umil-app:develop-latest + workingDir: /virtual-instrument-museum/vim-app + command: ["python", "manage.py", "collectstatic", "--noinput"] + envFrom: + - configMapRef: + name: umil-config + - secretRef: + name: umil-secret + volumeMounts: + - name: static-files + mountPath: /virtual-instrument-museum/static + + containers: + - name: app + image: ghcr.io/ddmal/umil-app:develop-latest + imagePullPolicy: Always + workingDir: /virtual-instrument-museum/vim-app + command: ["gunicorn", "-w", "2", "-b", "0:8001", "VIM.wsgi"] + ports: + - containerPort: 8001 + envFrom: + - configMapRef: + name: umil-config + - secretRef: + name: umil-secret + volumeMounts: + - name: static-files + mountPath: /virtual-instrument-museum/static + - name: media-files + mountPath: /virtual-instrument-museum/media + readinessProbe: + tcpSocket: + port: 8001 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 6 + livenessProbe: + tcpSocket: + port: 8001 + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" + + - name: nginx + image: ghcr.io/ddmal/umil-nginx:develop-latest + imagePullPolicy: Always + ports: + - containerPort: 80 + env: + - name: HOST_NAME + valueFrom: + configMapKeyRef: + name: umil-config + key: HOST_NAME + volumeMounts: + - name: static-files + mountPath: /virtual-instrument-museum/static + - name: media-files + mountPath: /virtual-instrument-museum/media + readinessProbe: + httpGet: + path: /static/ + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 6 + resources: + requests: + memory: "64Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "500m" + + volumes: + - name: static-files + emptyDir: {} + - name: media-files + persistentVolumeClaim: + claimName: vim-media \ No newline at end of file diff --git a/k8s/app/ingress.yaml b/k8s/app/ingress.yaml new file mode 100644 index 00000000..2ef9c3b8 --- /dev/null +++ b/k8s/app/ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: umil-ingress + namespace: umil +spec: + rules: + - host: umil.linkedmusic.ca + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: vim-nginx + port: + number: 80 \ No newline at end of file diff --git a/k8s/app/postgres-alias.yaml b/k8s/app/postgres-alias.yaml new file mode 100644 index 00000000..16508d72 --- /dev/null +++ b/k8s/app/postgres-alias.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Service +metadata: + name: vim-db + namespace: umil +spec: + type: ExternalName + externalName: postgres-umil.postgres.svc.cluster.local \ No newline at end of file diff --git a/k8s/app/pvc-media.yaml b/k8s/app/pvc-media.yaml new file mode 100644 index 00000000..7b172d8c --- /dev/null +++ b/k8s/app/pvc-media.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vim-media + namespace: umil +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 3Gi diff --git a/k8s/app/secret.yaml b/k8s/app/secret.yaml new file mode 100644 index 00000000..8ca21f61 --- /dev/null +++ b/k8s/app/secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: umil-secret + namespace: umil +type: Opaque +stringData: + DJANGO_SECRET_KEY: "k(#n*q9!k)1*c@0#n9vj1kz2op!6&btreze8ent&s=in_*a#%n" + POSTGRES_PASSWORD: "zH57w2KFGwPIzh0O" + EMAIL_HOST_PASSWORD: "BAzF4BtHhFE5hNxz1pBbqM/b+aZxNL++1sV9NATJWycD" diff --git a/k8s/app/service-app.yaml b/k8s/app/service-app.yaml new file mode 100644 index 00000000..fddf180e --- /dev/null +++ b/k8s/app/service-app.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: vim-app + namespace: umil +spec: + selector: + app: umil-app + ports: + - port: 8001 + targetPort: 8001 + type: ClusterIP \ No newline at end of file diff --git a/k8s/app/service-nginx.yaml b/k8s/app/service-nginx.yaml new file mode 100644 index 00000000..2db5579c --- /dev/null +++ b/k8s/app/service-nginx.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: vim-nginx + namespace: umil +spec: + selector: + app: umil-app + ports: + - port: 80 + targetPort: 80 + type: ClusterIP \ No newline at end of file From 51482b7a859d9749716f9c358e6db2d1e8aaaa2a Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 12:28:37 -0400 Subject: [PATCH 12/21] Fix: url of images --- .github/workflows/build-push.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 2c0800fa..b19917a1 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -36,8 +36,8 @@ jobs: target: prod push: true tags: | - ghcr.io/ddmal/umil-app:${{ steps.branch.outputs.name }}-latest - ghcr.io/ddmal/umil-app:${{ github.sha }} + ghcr.io/ddmal/umil/umil-app:${{ steps.branch.outputs.name }}-latest + ghcr.io/ddmal/umil/umil-app:${{ github.sha }} cache-from: type=gha,scope=app cache-to: type=gha,mode=max,scope=app @@ -48,7 +48,7 @@ jobs: file: ./nginx/Dockerfile.prod push: true tags: | - ghcr.io/ddmal/umil-nginx:${{ steps.branch.outputs.name }}-latest - ghcr.io/ddmal/umil-nginx:${{ github.sha }} + ghcr.io/ddmal/umil/umil-nginx:${{ steps.branch.outputs.name }}-latest + ghcr.io/ddmal/umil/umil-nginx:${{ github.sha }} cache-from: type=gha,scope=nginx cache-to: type=gha,mode=max,scope=nginx From 5f0922f391f66635cc5e9a1a2dd49fedcf4dac69 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 12:37:39 -0400 Subject: [PATCH 13/21] Fix: build-and-push image url --- .github/workflows/build-push.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index b19917a1..cd1e7312 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -28,6 +28,12 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/ddmal/umil + - name: Build and push app uses: docker/build-push-action@v5 with: @@ -36,8 +42,9 @@ jobs: target: prod push: true tags: | - ghcr.io/ddmal/umil/umil-app:${{ steps.branch.outputs.name }}-latest - ghcr.io/ddmal/umil/umil-app:${{ github.sha }} + ghcr.io/ddmal/umil-app:${{ steps.branch.outputs.name }}-latest + ghcr.io/ddmal/umil-app:${{ github.sha }} + labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=app cache-to: type=gha,mode=max,scope=app @@ -48,7 +55,8 @@ jobs: file: ./nginx/Dockerfile.prod push: true tags: | - ghcr.io/ddmal/umil/umil-nginx:${{ steps.branch.outputs.name }}-latest - ghcr.io/ddmal/umil/umil-nginx:${{ github.sha }} + ghcr.io/ddmal/umil-nginx:${{ steps.branch.outputs.name }}-latest + ghcr.io/ddmal/umil-nginx:${{ github.sha }} + labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=nginx cache-to: type=gha,mode=max,scope=nginx From 2a5c4e43ac3abebf9e13698ee19f064e97640e43 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 17:18:03 -0400 Subject: [PATCH 14/21] Fix: add local host to allowed hosts --- k8s/app/deployment.yaml | 29 +++++------------------------ nginx/vim.conf.template | 2 +- web-app/django/VIM/settings.py | 2 +- 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/k8s/app/deployment.yaml b/k8s/app/deployment.yaml index cdda3e29..00eb02b7 100644 --- a/k8s/app/deployment.yaml +++ b/k8s/app/deployment.yaml @@ -16,25 +16,8 @@ spec: imagePullSecrets: - name: ghcr-pull-secret initContainers: - - name: wait-for-db - image: busybox - command: - - /bin/sh - - -c - - until nc -z vim-db 5432; do echo "Waiting for postgres..."; sleep 2; done - - - name: migrate - image: ghcr.io/ddmal/umil-app:develop-latest - workingDir: /virtual-instrument-museum/vim-app - command: ["python", "manage.py", "migrate", "--noinput"] - envFrom: - - configMapRef: - name: umil-config - - secretRef: - name: umil-secret - - name: collectstatic - image: ghcr.io/ddmal/umil-app:develop-latest + image: ghcr.io/ddmal/umil-app:k8s-migration-latest workingDir: /virtual-instrument-museum/vim-app command: ["python", "manage.py", "collectstatic", "--noinput"] envFrom: @@ -45,10 +28,9 @@ spec: volumeMounts: - name: static-files mountPath: /virtual-instrument-museum/static - containers: - name: app - image: ghcr.io/ddmal/umil-app:develop-latest + image: ghcr.io/ddmal/umil-app:k8s-migration-latest imagePullPolicy: Always workingDir: /virtual-instrument-museum/vim-app command: ["gunicorn", "-w", "2", "-b", "0:8001", "VIM.wsgi"] @@ -85,7 +67,7 @@ spec: cpu: "1" - name: nginx - image: ghcr.io/ddmal/umil-nginx:develop-latest + image: ghcr.io/ddmal/umil-nginx:k8s-migration-latest imagePullPolicy: Always ports: - containerPort: 80 @@ -101,8 +83,7 @@ spec: - name: media-files mountPath: /virtual-instrument-museum/media readinessProbe: - httpGet: - path: /static/ + tcpSocket: port: 80 initialDelaySeconds: 5 periodSeconds: 5 @@ -120,4 +101,4 @@ spec: emptyDir: {} - name: media-files persistentVolumeClaim: - claimName: vim-media \ No newline at end of file + claimName: vim-media diff --git a/nginx/vim.conf.template b/nginx/vim.conf.template index b7ea437a..9df03033 100644 --- a/nginx/vim.conf.template +++ b/nginx/vim.conf.template @@ -13,7 +13,7 @@ server { listen 80; client_max_body_size 4G; - server_name ${HOST_NAME}; + server_name ${HOST_NAME} localhost; location /static/assets/ { alias /virtual-instrument-museum/frontend/assets/; diff --git a/web-app/django/VIM/settings.py b/web-app/django/VIM/settings.py index d9868793..eff48842 100644 --- a/web-app/django/VIM/settings.py +++ b/web-app/django/VIM/settings.py @@ -30,7 +30,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = IS_DEVELOPMENT -ALLOWED_HOSTS = [os.environ.get("HOST_NAME")] +ALLOWED_HOSTS = [os.environ.get("HOST_NAME"), "localhost", "127.0.0.1"] if DEBUG: import socket From ddc892049d6948d282537774b98aa7f1f16ac0e0 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 17:32:19 -0400 Subject: [PATCH 15/21] Feat: Add django-extension and ipython --- poetry.lock | 334 +++++++++++++++++++++++++++++++-- pyproject.toml | 3 +- web-app/django/VIM/settings.py | 3 +- 3 files changed, 327 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index a9ef58c6..ff7378ec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -6,6 +6,7 @@ version = "3.11.0" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.9" +groups = ["main", "debug", "dev"] files = [ {file = "asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d"}, {file = "asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4"}, @@ -20,6 +21,7 @@ version = "2.15.8" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, @@ -29,12 +31,29 @@ files = [ lazy-object-proxy = ">=1.4.0" wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} +[[package]] +name = "asttokens" +version = "3.0.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, + {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, +] + +[package.extras] +astroid = ["astroid (>=2,<5)"] +test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] + [[package]] name = "black" version = "26.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168"}, {file = "black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d"}, @@ -85,6 +104,7 @@ version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, @@ -96,6 +116,7 @@ version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, @@ -218,6 +239,7 @@ version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, @@ -232,10 +254,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\""} [[package]] name = "cssbeautifier" @@ -243,6 +267,7 @@ version = "1.15.4" description = "CSS unobfuscator and beautifier." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "cssbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:78c84d5e5378df7d08622bbd0477a1abdbd209680e95480bf22f12d5701efc98"}, {file = "cssbeautifier-1.15.4.tar.gz", hash = "sha256:9bb08dc3f64c101a01677f128acf01905914cf406baf87434dcde05b74c0acf5"}, @@ -253,12 +278,25 @@ editorconfig = ">=0.12.2" jsbeautifier = "*" six = ">=1.13.0" +[[package]] +name = "decorator" +version = "5.3.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c"}, + {file = "decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82"}, +] + [[package]] name = "dill" version = "0.4.1" description = "serialize all of Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, @@ -274,6 +312,7 @@ version = "4.2.5" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" +groups = ["main", "debug", "dev"] files = [ {file = "Django-4.2.5-py3-none-any.whl", hash = "sha256:b6b2b5cae821077f137dc4dade696a1c2aa292f892eca28fa8d7bfdf2608ddd4"}, {file = "Django-4.2.5.tar.gz", hash = "sha256:5e5c1c9548ffb7796b4a8a4782e9a2e5a3df3615259fc1bfd3ebc73b646146c1"}, @@ -294,6 +333,7 @@ version = "8.1.0" description = "Deletes old files." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "django-cleanup-8.1.0.tar.gz", hash = "sha256:70df905076a44e7a111b31198199af633dee08876e199e6dce36ca8dd6b8b10f"}, {file = "django_cleanup-8.1.0-py2.py3-none-any.whl", hash = "sha256:7903873ea73b3f7e61e055340d27dba49b70634f60c87a573ad748e172836458"}, @@ -305,6 +345,7 @@ version = "4.3.0" description = "A configurable set of panels that display various debug information about the current request/response." optional = false python-versions = ">=3.8" +groups = ["debug"] files = [ {file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"}, {file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"}, @@ -320,6 +361,7 @@ version = "3.2.3" description = "Extensions for Django" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"}, {file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"}, @@ -334,6 +376,7 @@ version = "4.1.0" description = "Cache-based rate-limiting for Django." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "django-ratelimit-4.1.0.tar.gz", hash = "sha256:555943b283045b917ad59f196829530d63be2a39adb72788d985b90c81ba808b"}, {file = "django_ratelimit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d047a31cf94d83ef1465d7543ca66c6fc16695559b5f8d814d1b51df15110b92"}, @@ -345,6 +388,7 @@ version = "5.2.9" description = "Mypy stubs for Django" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "django_stubs-5.2.9-py3-none-any.whl", hash = "sha256:2317a7130afdaa76f6ff7f623650d7f3bf1b6c86a60f95840e14e6ec6de1a7cd"}, {file = "django_stubs-5.2.9.tar.gz", hash = "sha256:c192257120b08785cfe6f2f1c91f1797aceae8e9daa689c336e52c91e8f6a493"}, @@ -367,6 +411,7 @@ version = "5.2.9" description = "Monkey-patching and extensions for django-stubs" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "django_stubs_ext-5.2.9-py3-none-any.whl", hash = "sha256:230c51575551b0165be40177f0f6805f1e3ebf799b835c85f5d64c371ca6cf71"}, {file = "django_stubs_ext-5.2.9.tar.gz", hash = "sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9"}, @@ -382,6 +427,7 @@ version = "3.1.0" description = "Integration of Vite in a Django project." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "django_vite-3.1.0-py3-none-any.whl", hash = "sha256:4e46572bd6b1ce70784be129205dc2ffcbc7a3c19fea50bebfb72b327bbde5fc"}, {file = "django_vite-3.1.0.tar.gz", hash = "sha256:8b4ffe4a9fa81ff568bfb195e74dde8694aaf13fd4b656ae60bf59cce08b85e8"}, @@ -399,6 +445,7 @@ version = "1.36.4" description = "HTML Template Linter and Formatter" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "djlint-1.36.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2dfb60883ceb92465201bfd392291a7597c6752baede6fbb6f1980cac8d6c5c"}, {file = "djlint-1.36.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc6a1320c0030244b530ac200642f883d3daa451a115920ef3d56d08b644292"}, @@ -441,6 +488,7 @@ version = "0.17.1" description = "EditorConfig File Locator and Interpreter for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82"}, {file = "editorconfig-0.17.1.tar.gz", hash = "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745"}, @@ -449,12 +497,28 @@ files = [ [package.extras] dev = ["mypy (>=1.15)"] +[[package]] +name = "executing" +version = "2.2.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, + {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] + [[package]] name = "gunicorn" version = "21.2.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, @@ -475,6 +539,7 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -483,12 +548,51 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "ipython" +version = "8.39.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f"}, + {file = "ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack_data = "*" +traitlets = ">=5.13.0" +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + [[package]] name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -497,12 +601,32 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jedi" +version = "0.20.0" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67"}, + {file = "jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011"}, +] + +[package.dependencies] +parso = ">=0.8.6,<0.9.0" + +[package.extras] +dev = ["Django", "attrs", "colorama", "docopt", "flake8 (==7.1.2)", "pytest (<9.0.0)", "types-setuptools (==80.9.0.20250529)", "typing-extensions", "zuban (==0.7.0)"] +docs = ["Jinja2 (==3.1.6)", "MarkupSafe (==3.0.3)", "Pygments (==2.20.0)", "Sphinx (==9.1.0)", "alabaster (==1.0.0)", "babel (==2.18.0)", "certifi (==2026.4.22)", "charset-normalizer (==3.4.7)", "docutils (==0.22.4)", "idna (==3.13)", "imagesize (==2.0.0)", "iniconfig (==2.3.0)", "packaging (==26.2)", "pluggy (==1.6.0)", "pytest (==9.0.3)", "requests (==2.33.1)", "roman-numerals (==4.1.0)", "snowballstemmer (==3.0.1)", "sphinx-rtd-theme (==3.1.0)", "sphinxcontrib-applehelp (==2.0.0)", "sphinxcontrib-devhelp (==2.0.0)", "sphinxcontrib-htmlhelp (==2.1.0)", "sphinxcontrib-jquery (==4.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==2.0.0)", "sphinxcontrib-serializinghtml (==2.0.0)", "urllib3 (==2.6.3)"] + [[package]] name = "jsbeautifier" version = "1.15.4" description = "JavaScript unobfuscator and beautifier." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "jsbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528"}, {file = "jsbeautifier-1.15.4.tar.gz", hash = "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592"}, @@ -518,6 +642,7 @@ version = "0.13.0" description = "A Python implementation of the JSON5 data format." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc"}, {file = "json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf"}, @@ -529,6 +654,7 @@ version = "1.12.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519"}, {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6"}, @@ -582,6 +708,8 @@ version = "0.7.8" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d"}, {file = "librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b"}, @@ -667,6 +795,7 @@ version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["debug"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -759,12 +888,31 @@ files = [ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6"}, + {file = "matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79"}, +] + +[package.dependencies] +traitlets = "*" + +[package.extras] +test = ["flake8", "matplotlib", "nbdime", "nbval", "notebook", "pytest"] + [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -776,6 +924,7 @@ version = "1.19.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, @@ -836,6 +985,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -847,17 +997,35 @@ version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] +[[package]] +name = "parso" +version = "0.8.7" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c"}, + {file = "parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "types-setuptools (==67.2.0.1)", "zuban (==0.5.1)"] +testing = ["docopt", "pytest"] + [[package]] name = "pathspec" version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -869,12 +1037,29 @@ optional = ["typing-extensions (>=4)"] re2 = ["google-re2 (>=1.1)"] tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + [[package]] name = "pillow" version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, @@ -963,7 +1148,7 @@ docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -972,6 +1157,7 @@ version = "4.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, @@ -982,12 +1168,28 @@ docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx- test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] type = ["mypy (>=1.18.2)"] +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, + {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, +] + +[package.dependencies] +wcwidth = "*" + [[package]] name = "psycopg" version = "3.1.10" description = "PostgreSQL database adapter for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "psycopg-3.1.10-py3-none-any.whl", hash = "sha256:8bbeddae5075c7890b2fa3e3553440376d3c5e28418335dee3c3656b06fa2b52"}, {file = "psycopg-3.1.10.tar.gz", hash = "sha256:15b25741494344c24066dc2479b0f383dd1b82fa5e75612fa4fa5bb30726e9b6"}, @@ -1005,19 +1207,63 @@ docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)" pool = ["psycopg-pool"] test = ["anyio (>=3.6.2)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pylint" version = "2.17.7" description = "python code static checker" optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, ] [package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" +astroid = ">=2.15.8,<=2.17.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} isort = ">=4.2.5,<6" @@ -1035,6 +1281,7 @@ version = "3.10.0" description = "Lightweight Python client for Apache Solr" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pysolr-3.10.0.tar.gz", hash = "sha256:127b4a2dd169234acb1586643a6cd1e3e94b917921e69bf569d7b2a2aa0ef409"}, ] @@ -1052,6 +1299,7 @@ version = "0.4.1" description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, @@ -1106,6 +1354,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -1188,6 +1437,7 @@ version = "2026.1.15" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e"}, {file = "regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f"}, @@ -1328,6 +1578,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -1349,19 +1600,20 @@ version = "80.10.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173"}, {file = "setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -1369,6 +1621,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1380,6 +1633,7 @@ version = "0.5.5" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" +groups = ["main", "debug", "dev"] files = [ {file = "sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba"}, {file = "sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e"}, @@ -1389,12 +1643,33 @@ files = [ dev = ["build"] doc = ["sphinx"] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "tomlkit" version = "0.14.0" description = "Style preserving TOML library" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680"}, {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, @@ -1406,6 +1681,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -1421,12 +1697,29 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "traitlets" +version = "5.15.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40"}, + {file = "traitlets-5.15.0.tar.gz", hash = "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "mypy (>=1.7.0,<1.19) ; platform_python_implementation == \"PyPy\"", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + [[package]] name = "types-pyyaml" version = "6.0.12.20250915" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6"}, {file = "types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3"}, @@ -1438,6 +1731,7 @@ version = "2.32.4.20260107" description = "Typing stubs for requests" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d"}, {file = "types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f"}, @@ -1452,6 +1746,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -1463,6 +1758,8 @@ version = "2025.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main", "debug", "dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, @@ -1474,16 +1771,29 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] + +[[package]] +name = "wcwidth" +version = "0.7.0" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2"}, + {file = "wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0"}, +] [[package]] name = "werkzeug" @@ -1491,6 +1801,7 @@ version = "3.0.0" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" +groups = ["debug"] files = [ {file = "werkzeug-3.0.0-py3-none-any.whl", hash = "sha256:cbb2600f7eabe51dbc0502f58be0b3e1b96b893b05695ea2b35b43d4de2d9962"}, {file = "werkzeug-3.0.0.tar.gz", hash = "sha256:3ffff4dcc32db52ef3cc94dff3000a3c2846890f3a5a51800a27b909c5e770f0"}, @@ -1508,6 +1819,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -1593,6 +1905,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.11" -content-hash = "52fca2fb4da33dac5fc0efcece2e6c24de59097c61170ef0bf728252990ecb01" +content-hash = "e5c6259b525e755366f4ac8662970ffdf3130ea7eff290fa8c898b374c3b4152" diff --git a/pyproject.toml b/pyproject.toml index 25e13f55..e383649d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ django-vite = "^3.1.0" pysolr = "^3.10.0" django-cleanup = "^8.0.0" django-ratelimit = "^4.1.0" +django-extensions = "3.2.3" +ipython = "^8.0.0" [tool.poetry.group.dev.dependencies] @@ -31,7 +33,6 @@ djlint = "^1.34.1" [tool.poetry.group.debug.dependencies] django-debug-toolbar = "^4.2.0" -django-extensions = "3.2.3" werkzeug = "3.0.0" [build-system] diff --git a/web-app/django/VIM/settings.py b/web-app/django/VIM/settings.py index eff48842..b10fb252 100644 --- a/web-app/django/VIM/settings.py +++ b/web-app/django/VIM/settings.py @@ -51,11 +51,12 @@ "VIM.apps.main", "VIM.apps.instruments", "django_vite", + "django_extensions", "django_cleanup.apps.CleanupConfig", # Must be last - handles orphaned file cleanup ] if IS_DEVELOPMENT: - INSTALLED_APPS += ["django_extensions", "debug_toolbar"] + INSTALLED_APPS += ["debug_toolbar"] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", From c2d5b7bd3f252052ec04e033ced085f47eb6015c Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 18:10:14 -0400 Subject: [PATCH 16/21] Add access-log in gunicorn --- k8s/app/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/app/deployment.yaml b/k8s/app/deployment.yaml index 00eb02b7..36b7c2e2 100644 --- a/k8s/app/deployment.yaml +++ b/k8s/app/deployment.yaml @@ -33,7 +33,7 @@ spec: image: ghcr.io/ddmal/umil-app:k8s-migration-latest imagePullPolicy: Always workingDir: /virtual-instrument-museum/vim-app - command: ["gunicorn", "-w", "2", "-b", "0:8001", "VIM.wsgi"] + command: ["gunicorn", "-w", "2", "-b", "0:8001", "--access-logfile", "-", "VIM.wsgi"] ports: - containerPort: 8001 envFrom: From 79025ca4bcc105711b888471b6e0b52c25ff0664 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 18:18:07 -0400 Subject: [PATCH 17/21] Feat: add deploy job to build-push-deploy workflow --- .../{build-push.yml => build-push-deploy.yml} | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) rename .github/workflows/{build-push.yml => build-push-deploy.yml} (67%) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push-deploy.yml similarity index 67% rename from .github/workflows/build-push.yml rename to .github/workflows/build-push-deploy.yml index cd1e7312..02b258a1 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push-deploy.yml @@ -1,7 +1,9 @@ -name: Build, Push +name: Build, Push, and Deploy on: push: + release: + types: [published] permissions: contents: read @@ -60,3 +62,26 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=nginx cache-to: type=gha,mode=max,scope=nginx + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - name: Set up kubectl + uses: azure/setup-kubectl@v4 + + - name: Configure kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Deploy to Kubernetes + run: | + kubectl set image deployment/umil-app \ + collectstatic=ghcr.io/ddmal/umil-app:${{ github.sha }} \ + app=ghcr.io/ddmal/umil-app:${{ github.sha }} \ + nginx=ghcr.io/ddmal/umil-nginx:${{ github.sha }} \ + -n umil + kubectl rollout status deployment/umil-app -n umil --timeout=300s From 3cedf5132cf56138aaf03da928e5c0d138e5452f Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 23:11:49 -0400 Subject: [PATCH 18/21] Feat: Add staging k8s manifests --- .github/workflows/build-push-deploy.yml | 25 ++- .github/workflows/cd_prod.yml | 103 ---------- .github/workflows/cd_stage.yml | 47 ----- k8s/{ => production}/app/configmap.yaml | 2 + k8s/{ => production}/app/deployment.yaml | 0 k8s/{ => production}/app/ingress.yaml | 0 k8s/{ => production}/app/postgres-alias.yaml | 0 k8s/{ => production}/app/pvc-media.yaml | 0 k8s/{ => production}/app/secret.yaml | 0 k8s/{ => production}/app/service-app.yaml | 0 k8s/{ => production}/app/service-nginx.yaml | 0 k8s/{ => production}/postgres/pvc.yaml | 0 k8s/{ => production}/postgres/secret.yaml | 0 k8s/{ => production}/postgres/service.yaml | 0 .../postgres/statefulset.yaml | 0 k8s/{ => production}/solr/configmap.yaml | 0 k8s/{ => production}/solr/pvc.yaml | 0 k8s/{ => production}/solr/service.yaml | 0 k8s/{ => production}/solr/statefulset.yaml | 0 k8s/staging/app/configmap.yaml | 16 ++ k8s/staging/app/deployment.yaml | 104 ++++++++++ k8s/staging/app/ingress.yaml | 17 ++ k8s/staging/app/postgres-alias.yaml | 8 + k8s/staging/app/pvc-media.yaml | 12 ++ k8s/staging/app/secret.yaml | 10 + k8s/staging/app/service-app.yaml | 12 ++ k8s/staging/app/service-nginx.yaml | 12 ++ k8s/staging/postgres/pvc.yaml | 12 ++ k8s/staging/postgres/secret.yaml | 8 + k8s/staging/postgres/service.yaml | 12 ++ k8s/staging/postgres/statefulset.yaml | 74 +++++++ k8s/staging/solr/configmap.yaml | 194 ++++++++++++++++++ k8s/staging/solr/pvc.yaml | 12 ++ k8s/staging/solr/service.yaml | 12 ++ k8s/staging/solr/statefulset.yaml | 87 ++++++++ nginx/vim.conf.template | 2 +- web-app/django/VIM/settings.py | 4 +- 37 files changed, 631 insertions(+), 154 deletions(-) delete mode 100644 .github/workflows/cd_prod.yml delete mode 100644 .github/workflows/cd_stage.yml rename k8s/{ => production}/app/configmap.yaml (81%) rename k8s/{ => production}/app/deployment.yaml (100%) rename k8s/{ => production}/app/ingress.yaml (100%) rename k8s/{ => production}/app/postgres-alias.yaml (100%) rename k8s/{ => production}/app/pvc-media.yaml (100%) rename k8s/{ => production}/app/secret.yaml (100%) rename k8s/{ => production}/app/service-app.yaml (100%) rename k8s/{ => production}/app/service-nginx.yaml (100%) rename k8s/{ => production}/postgres/pvc.yaml (100%) rename k8s/{ => production}/postgres/secret.yaml (100%) rename k8s/{ => production}/postgres/service.yaml (100%) rename k8s/{ => production}/postgres/statefulset.yaml (100%) rename k8s/{ => production}/solr/configmap.yaml (100%) rename k8s/{ => production}/solr/pvc.yaml (100%) rename k8s/{ => production}/solr/service.yaml (100%) rename k8s/{ => production}/solr/statefulset.yaml (100%) create mode 100644 k8s/staging/app/configmap.yaml create mode 100644 k8s/staging/app/deployment.yaml create mode 100644 k8s/staging/app/ingress.yaml create mode 100644 k8s/staging/app/postgres-alias.yaml create mode 100644 k8s/staging/app/pvc-media.yaml create mode 100644 k8s/staging/app/secret.yaml create mode 100644 k8s/staging/app/service-app.yaml create mode 100644 k8s/staging/app/service-nginx.yaml create mode 100644 k8s/staging/postgres/pvc.yaml create mode 100644 k8s/staging/postgres/secret.yaml create mode 100644 k8s/staging/postgres/service.yaml create mode 100644 k8s/staging/postgres/statefulset.yaml create mode 100644 k8s/staging/solr/configmap.yaml create mode 100644 k8s/staging/solr/pvc.yaml create mode 100644 k8s/staging/solr/service.yaml create mode 100644 k8s/staging/solr/statefulset.yaml diff --git a/.github/workflows/build-push-deploy.yml b/.github/workflows/build-push-deploy.yml index 02b258a1..a55a3b08 100644 --- a/.github/workflows/build-push-deploy.yml +++ b/.github/workflows/build-push-deploy.yml @@ -63,7 +63,30 @@ jobs: cache-from: type=gha,scope=nginx cache-to: type=gha,mode=max,scope=nginx - deploy: + deploy-staging: + needs: build-and-push + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + steps: + - name: Set up kubectl + uses: azure/setup-kubectl@v4 + + - name: Configure kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Deploy to staging + run: | + kubectl set image deployment/umil-app-staging \ + collectstatic=ghcr.io/ddmal/umil-app:${{ github.sha }} \ + app=ghcr.io/ddmal/umil-app:${{ github.sha }} \ + nginx=ghcr.io/ddmal/umil-nginx:${{ github.sha }} \ + -n umil + kubectl rollout status deployment/umil-app-staging -n umil --timeout=300s + + deploy-production: needs: build-and-push runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' diff --git a/.github/workflows/cd_prod.yml b/.github/workflows/cd_prod.yml deleted file mode 100644 index 404b7e91..00000000 --- a/.github/workflows/cd_prod.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Deploy to Production Server - -on: - release: - types: [published] - workflow_dispatch: - inputs: - version: - description: "Release version to deploy (e.g., v1.0.0). Leave empty for latest main." - required: false - type: string - -concurrency: - group: production - cancel-in-progress: false # In prod, queue the deployment if multiple are requested - -jobs: - deploy-production: - runs-on: ubuntu-latest - env: - DEPLOY_VERSION: ${{ github.event.inputs.version || github.event.release.tag_name || 'main' }} - steps: - - name: Checkout the current repo - uses: actions/checkout@v5 - - name: Install SSH Key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY }} - known_hosts: ${{ secrets.KNOWN_HOSTS }} - - name: Validate deploy version input - run: | - set -euo pipefail - if [[ "$DEPLOY_VERSION" == "main" ]]; then - echo "SAFE_DEPLOY_VERSION=$DEPLOY_VERSION" >> "$GITHUB_ENV" - exit 0 - fi - if [[ "$DEPLOY_VERSION" =~ ^v[0-9]+(\.[0-9]+){2}(-[0-9A-Za-z.-]+)?$ ]]; then - echo "SAFE_DEPLOY_VERSION=$DEPLOY_VERSION" >> "$GITHUB_ENV" - exit 0 - fi - if [[ "$DEPLOY_VERSION" =~ ^[0-9a-f]{7,40}$ ]]; then - echo "SAFE_DEPLOY_VERSION=$DEPLOY_VERSION" >> "$GITHUB_ENV" - exit 0 - fi - echo "::error::Invalid deploy version format: $DEPLOY_VERSION" - exit 1 - - name: Record current deployed version for rollback - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST_PROD }} ${{ secrets.USERNAME }}@${{ secrets.HOST_PROD }} \ - "cd /virtual-instrument-museum && - sudo bash -c 'CURRENT_VERSION=\$(git describe --tags --exact-match 2>/dev/null || git rev-parse HEAD); echo \"\$CURRENT_VERSION\" > /virtual-instrument-museum/.last_deployed'" - - name: Fetch updates and validate target version - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST_PROD }} ${{ secrets.USERNAME }}@${{ secrets.HOST_PROD }} \ - "set -euo pipefail; - VERSION=\"${SAFE_DEPLOY_VERSION}\"; - cd /virtual-instrument-museum; - sudo git fetch origin -v --tags; - if [ \"\$VERSION\" != 'main' ]; then - if ! sudo git rev-parse --verify --quiet \"\$VERSION^{commit}\" >/dev/null; then - echo \"::error::Invalid deploy version: \$VERSION\"; - exit 1; - fi; - fi" - - name: Stop the current Docker containers running - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST_PROD }} ${{ secrets.USERNAME }}@${{ secrets.HOST_PROD }} \ - "cd /virtual-instrument-museum && - sudo docker compose stop" - - name: Deploy target version - id: deploy_target - continue-on-error: true - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST_PROD }} ${{ secrets.USERNAME }}@${{ secrets.HOST_PROD }} \ - "set -euo pipefail; - VERSION=\"${SAFE_DEPLOY_VERSION}\"; - cd /virtual-instrument-museum; - sudo git checkout \"\$VERSION\"; - if [ \"\$VERSION\" = 'main' ]; then sudo git pull origin main; fi; - sudo docker compose -f docker-compose-deployment.yml build --no-cache; - sudo docker compose -f docker-compose-deployment.yml up -d --wait --wait-timeout 180" - - name: Roll back to last deployed version - if: ${{ steps.deploy_target.outcome != 'success' }} - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST_PROD }} ${{ secrets.USERNAME }}@${{ secrets.HOST_PROD }} \ - "set -euo pipefail; - cd /virtual-instrument-museum; - if [ ! -f /virtual-instrument-museum/.last_deployed ]; then - echo 'No rollback version found'; - exit 1; - fi; - ROLLBACK_VERSION=\$(cat /virtual-instrument-museum/.last_deployed); - sudo git checkout \$ROLLBACK_VERSION; - sudo docker compose -f docker-compose-deployment.yml build --no-cache; - sudo docker compose -f docker-compose-deployment.yml up -d --wait --wait-timeout 180; - echo '::error::Rollback executed; marking workflow as failed.'; - exit 1" - - name: Record successful deploy version - if: ${{ steps.deploy_target.outcome == 'success' }} - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST_PROD }} ${{ secrets.USERNAME }}@${{ secrets.HOST_PROD }} \ - "cd /virtual-instrument-museum && - sudo bash -c 'CURRENT_VERSION=\$(git describe --tags --exact-match 2>/dev/null || git rev-parse HEAD); echo \"\$CURRENT_VERSION\" > /virtual-instrument-museum/.last_deployed; echo \"Deployed: \$CURRENT_VERSION\"'" diff --git a/.github/workflows/cd_stage.yml b/.github/workflows/cd_stage.yml deleted file mode 100644 index bde0db9c..00000000 --- a/.github/workflows/cd_stage.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Deploy to staging Server -on: - push: - branches: - - "develop" - -concurrency: - group: staging - cancel-in-progress: true - -jobs: - deploy-staging: - runs-on: ubuntu-latest - steps: - - name: Checkout to source code - uses: actions/checkout@v5 - - name: Install SSH Key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.KEY }} - known_hosts: ${{ secrets.KNOWN_HOSTS }} - - name: Stop docker swarm and cleanup services - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST2 }} ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ - "cd /virtual_instrument_museum && - sudo docker compose stop" - - name: Log git branch we're on - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST2 }} ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ - "cd /virtual_instrument_museum && - sudo git branch --all -v" - - name: Fetch on dev branch - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST2 }} ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ - "cd /virtual_instrument_museum && - sudo git fetch origin develop && - sudo git merge origin/develop" - - name: Build docker images - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST2 }} ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ - "cd /virtual_instrument_museum && - sudo docker compose -f docker-compose-deployment.yml build --no-cache" - - name: Start the service - run: | - ssh -J ${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST1 }},${{ secrets.PROXY_USERNAME }}@${{ secrets.PROXY_HOST2 }} ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ - "cd /virtual_instrument_museum && - sudo docker compose -f docker-compose-deployment.yml up -d" diff --git a/k8s/app/configmap.yaml b/k8s/production/app/configmap.yaml similarity index 81% rename from k8s/app/configmap.yaml rename to k8s/production/app/configmap.yaml index 403d6917..fa331257 100644 --- a/k8s/app/configmap.yaml +++ b/k8s/production/app/configmap.yaml @@ -12,4 +12,6 @@ data: DEFAULT_FROM_EMAIL: "UMIL " POSTGRES_USER: "vim_admin" POSTGRES_DB: "virtual_instrument_museum" + POSTGRES_HOST: "vim-db" + SOLR_URL: "http://solr:8983/solr/virtual-instrument-museum" diff --git a/k8s/app/deployment.yaml b/k8s/production/app/deployment.yaml similarity index 100% rename from k8s/app/deployment.yaml rename to k8s/production/app/deployment.yaml diff --git a/k8s/app/ingress.yaml b/k8s/production/app/ingress.yaml similarity index 100% rename from k8s/app/ingress.yaml rename to k8s/production/app/ingress.yaml diff --git a/k8s/app/postgres-alias.yaml b/k8s/production/app/postgres-alias.yaml similarity index 100% rename from k8s/app/postgres-alias.yaml rename to k8s/production/app/postgres-alias.yaml diff --git a/k8s/app/pvc-media.yaml b/k8s/production/app/pvc-media.yaml similarity index 100% rename from k8s/app/pvc-media.yaml rename to k8s/production/app/pvc-media.yaml diff --git a/k8s/app/secret.yaml b/k8s/production/app/secret.yaml similarity index 100% rename from k8s/app/secret.yaml rename to k8s/production/app/secret.yaml diff --git a/k8s/app/service-app.yaml b/k8s/production/app/service-app.yaml similarity index 100% rename from k8s/app/service-app.yaml rename to k8s/production/app/service-app.yaml diff --git a/k8s/app/service-nginx.yaml b/k8s/production/app/service-nginx.yaml similarity index 100% rename from k8s/app/service-nginx.yaml rename to k8s/production/app/service-nginx.yaml diff --git a/k8s/postgres/pvc.yaml b/k8s/production/postgres/pvc.yaml similarity index 100% rename from k8s/postgres/pvc.yaml rename to k8s/production/postgres/pvc.yaml diff --git a/k8s/postgres/secret.yaml b/k8s/production/postgres/secret.yaml similarity index 100% rename from k8s/postgres/secret.yaml rename to k8s/production/postgres/secret.yaml diff --git a/k8s/postgres/service.yaml b/k8s/production/postgres/service.yaml similarity index 100% rename from k8s/postgres/service.yaml rename to k8s/production/postgres/service.yaml diff --git a/k8s/postgres/statefulset.yaml b/k8s/production/postgres/statefulset.yaml similarity index 100% rename from k8s/postgres/statefulset.yaml rename to k8s/production/postgres/statefulset.yaml diff --git a/k8s/solr/configmap.yaml b/k8s/production/solr/configmap.yaml similarity index 100% rename from k8s/solr/configmap.yaml rename to k8s/production/solr/configmap.yaml diff --git a/k8s/solr/pvc.yaml b/k8s/production/solr/pvc.yaml similarity index 100% rename from k8s/solr/pvc.yaml rename to k8s/production/solr/pvc.yaml diff --git a/k8s/solr/service.yaml b/k8s/production/solr/service.yaml similarity index 100% rename from k8s/solr/service.yaml rename to k8s/production/solr/service.yaml diff --git a/k8s/solr/statefulset.yaml b/k8s/production/solr/statefulset.yaml similarity index 100% rename from k8s/solr/statefulset.yaml rename to k8s/production/solr/statefulset.yaml diff --git a/k8s/staging/app/configmap.yaml b/k8s/staging/app/configmap.yaml new file mode 100644 index 00000000..193eedd0 --- /dev/null +++ b/k8s/staging/app/configmap.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: umil-config-staging + namespace: umil +data: + MODE: "prod" + HOST_NAME: "umil.staging.linkedmusic.ca" + EMAIL_HOST: "email-smtp.us-west-2.amazonaws.com" + EMAIL_PORT: "587" + EMAIL_HOST_USER: "AKIASGTLL5NYVV7JG65A" + DEFAULT_FROM_EMAIL: "UMIL " + POSTGRES_USER: "vim_admin" + POSTGRES_DB: "virtual_instrument_museum_staging" + POSTGRES_HOST: "vim-db-staging" + SOLR_URL: "http://solr-staging:8983/solr/virtual-instrument-museum" \ No newline at end of file diff --git a/k8s/staging/app/deployment.yaml b/k8s/staging/app/deployment.yaml new file mode 100644 index 00000000..cd67c6ee --- /dev/null +++ b/k8s/staging/app/deployment.yaml @@ -0,0 +1,104 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: umil-app-staging + namespace: umil +spec: + replicas: 1 + selector: + matchLabels: + app: umil-app-staging + template: + metadata: + labels: + app: umil-app-staging + spec: + imagePullSecrets: + - name: ghcr-pull-secret + initContainers: + - name: collectstatic + image: ghcr.io/ddmal/umil-app:k8s-migration-latest + workingDir: /virtual-instrument-museum/vim-app + command: ["python", "manage.py", "collectstatic", "--noinput"] + envFrom: + - configMapRef: + name: umil-config-staging + - secretRef: + name: umil-secret-staging + volumeMounts: + - name: static-files + mountPath: /virtual-instrument-museum/static + containers: + - name: app + image: ghcr.io/ddmal/umil-app:k8s-migration-latest + imagePullPolicy: Always + workingDir: /virtual-instrument-museum/vim-app + command: ["gunicorn", "-w", "2", "-b", "0:8001", "--access-logfile", "-", "VIM.wsgi"] + ports: + - containerPort: 8001 + envFrom: + - configMapRef: + name: umil-config-staging + - secretRef: + name: umil-secret-staging + volumeMounts: + - name: static-files + mountPath: /virtual-instrument-museum/static + - name: media-files + mountPath: /virtual-instrument-museum/media + readinessProbe: + tcpSocket: + port: 8001 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 6 + livenessProbe: + tcpSocket: + port: 8001 + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" + + - name: nginx + image: ghcr.io/ddmal/umil-nginx:k8s-migration-latest + imagePullPolicy: Always + ports: + - containerPort: 80 + env: + - name: HOST_NAME + valueFrom: + configMapKeyRef: + name: umil-config-staging + key: HOST_NAME + volumeMounts: + - name: static-files + mountPath: /virtual-instrument-museum/static + - name: media-files + mountPath: /virtual-instrument-museum/media + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 6 + resources: + requests: + memory: "64Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "500m" + + volumes: + - name: static-files + emptyDir: {} + - name: media-files + persistentVolumeClaim: + claimName: vim-media-staging diff --git a/k8s/staging/app/ingress.yaml b/k8s/staging/app/ingress.yaml new file mode 100644 index 00000000..adf9dbfa --- /dev/null +++ b/k8s/staging/app/ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: umil-ingress-staging + namespace: umil +spec: + rules: + - host: umil.staging.linkedmusic.ca + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: vim-nginx-staging + port: + number: 80 \ No newline at end of file diff --git a/k8s/staging/app/postgres-alias.yaml b/k8s/staging/app/postgres-alias.yaml new file mode 100644 index 00000000..9f4aec22 --- /dev/null +++ b/k8s/staging/app/postgres-alias.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Service +metadata: + name: vim-db-staging + namespace: umil +spec: + type: ExternalName + externalName: postgres-umil-staging.postgres.svc.cluster.local \ No newline at end of file diff --git a/k8s/staging/app/pvc-media.yaml b/k8s/staging/app/pvc-media.yaml new file mode 100644 index 00000000..d85405e1 --- /dev/null +++ b/k8s/staging/app/pvc-media.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vim-media-staging + namespace: umil +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 3Gi \ No newline at end of file diff --git a/k8s/staging/app/secret.yaml b/k8s/staging/app/secret.yaml new file mode 100644 index 00000000..9fc8eb69 --- /dev/null +++ b/k8s/staging/app/secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: umil-secret-staging + namespace: umil +type: Opaque +stringData: + DJANGO_SECRET_KEY: "" + POSTGRES_PASSWORD: "" + EMAIL_HOST_PASSWORD: "" diff --git a/k8s/staging/app/service-app.yaml b/k8s/staging/app/service-app.yaml new file mode 100644 index 00000000..74886256 --- /dev/null +++ b/k8s/staging/app/service-app.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: vim-app-staging + namespace: umil +spec: + selector: + app: umil-app-staging + ports: + - port: 8001 + targetPort: 8001 + type: ClusterIP \ No newline at end of file diff --git a/k8s/staging/app/service-nginx.yaml b/k8s/staging/app/service-nginx.yaml new file mode 100644 index 00000000..653239d1 --- /dev/null +++ b/k8s/staging/app/service-nginx.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: vim-nginx-staging + namespace: umil +spec: + selector: + app: umil-app-staging + ports: + - port: 80 + targetPort: 80 + type: ClusterIP \ No newline at end of file diff --git a/k8s/staging/postgres/pvc.yaml b/k8s/staging/postgres/pvc.yaml new file mode 100644 index 00000000..bce788ed --- /dev/null +++ b/k8s/staging/postgres/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-umil-staging-pvc + namespace: postgres +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/k8s/staging/postgres/secret.yaml b/k8s/staging/postgres/secret.yaml new file mode 100644 index 00000000..b6fd86ab --- /dev/null +++ b/k8s/staging/postgres/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-umil-staging-secret + namespace: postgres +type: Opaque +stringData: + POSTGRES_PASSWORD: "" diff --git a/k8s/staging/postgres/service.yaml b/k8s/staging/postgres/service.yaml new file mode 100644 index 00000000..5ea2b657 --- /dev/null +++ b/k8s/staging/postgres/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-umil-staging + namespace: postgres +spec: + selector: + app: postgres-umil-staging + ports: + - port: 5432 + targetPort: 5432 + type: ClusterIP \ No newline at end of file diff --git a/k8s/staging/postgres/statefulset.yaml b/k8s/staging/postgres/statefulset.yaml new file mode 100644 index 00000000..845abb6f --- /dev/null +++ b/k8s/staging/postgres/statefulset.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgres-umil-staging + namespace: postgres +spec: + serviceName: postgres-umil-staging + replicas: 1 + selector: + matchLabels: + app: postgres-umil-staging + template: + metadata: + labels: + app: postgres-umil-staging + spec: + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: database + nodeSelector: + workload: database + containers: + - name: postgres + image: postgres:15.4 + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: virtual_instrument_museum_staging + - name: POSTGRES_USER + value: vim_admin + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-umil-staging-secret + key: POSTGRES_PASSWORD + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-umil-staging-pvc diff --git a/k8s/staging/solr/configmap.yaml b/k8s/staging/solr/configmap.yaml new file mode 100644 index 00000000..f73e737d --- /dev/null +++ b/k8s/staging/solr/configmap.yaml @@ -0,0 +1,194 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: solr-core-config-staging + namespace: umil +data: + core.properties: | + name=virtual-instrument-museum + schema.xml: | + + + + + sid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + solrconfig.xml: | + + + + + + + 9.4.2 + ${solr.data.dir:/var/solr} + + + + + true + + + + + ${solr.ulog.dir:} + ${solr.ulog.numVersionBuckets:65536} + + + ${solr.autoCommit.maxTime:15000} + false + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + 1024 + + + + true + 20 + 200 + false + 2 + + + + + + + + + + explicit + 10 + text + + + + + + + explicit + json + true + text + + + + + text + + + + \ No newline at end of file diff --git a/k8s/staging/solr/pvc.yaml b/k8s/staging/solr/pvc.yaml new file mode 100644 index 00000000..09ffccd4 --- /dev/null +++ b/k8s/staging/solr/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: solr-data-staging + namespace: umil +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/k8s/staging/solr/service.yaml b/k8s/staging/solr/service.yaml new file mode 100644 index 00000000..47c67678 --- /dev/null +++ b/k8s/staging/solr/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: solr-staging + namespace: umil +spec: + selector: + app: umil-solr-staging + ports: + - port: 8983 + targetPort: 8983 + type: ClusterIP \ No newline at end of file diff --git a/k8s/staging/solr/statefulset.yaml b/k8s/staging/solr/statefulset.yaml new file mode 100644 index 00000000..a98c306b --- /dev/null +++ b/k8s/staging/solr/statefulset.yaml @@ -0,0 +1,87 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: umil-solr-staging + namespace: umil +spec: + serviceName: solr-staging + replicas: 1 + selector: + matchLabels: + app: umil-solr-staging + template: + metadata: + labels: + app: umil-solr-staging + spec: + securityContext: + fsGroup: 8983 + initContainers: + - name: setup-core + image: busybox + securityContext: + runAsUser: 0 + command: + - /bin/sh + - -c + - | + CORE_DIR=/var/solr/data/virtual-instrument-museum + if [ ! -d "$CORE_DIR" ]; then + echo "Initializing Solr core..." + mkdir -p "$CORE_DIR/conf" + cp /tmp/core-config/core.properties "$CORE_DIR/" + cp /tmp/core-config/schema.xml "$CORE_DIR/conf/" + cp /tmp/core-config/solrconfig.xml "$CORE_DIR/conf/" + chown -R 8983:8983 /var/solr/data + echo "Solr core initialized at $CORE_DIR" + else + echo "Solr core already exists, skipping setup" + fi + volumeMounts: + - name: solr-data-staging + mountPath: /var/solr/data + - name: core-config + mountPath: /tmp/core-config + + containers: + - name: solr + image: solr:9.2.1 + ports: + - containerPort: 8983 + env: + - name: SOLR_SECURITY_MANAGER_ENABLED + value: "false" + volumeMounts: + - name: solr-data-staging + mountPath: /var/solr/data + readinessProbe: + httpGet: + path: /solr/virtual-instrument-museum/admin/ping + port: 8983 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /solr/virtual-instrument-museum/admin/ping + port: 8983 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1" + + volumes: + - name: solr-data-staging + persistentVolumeClaim: + claimName: solr-data-staging + - name: core-config + configMap: + name: solr-core-config-staging \ No newline at end of file diff --git a/nginx/vim.conf.template b/nginx/vim.conf.template index 9df03033..8ae746c0 100644 --- a/nginx/vim.conf.template +++ b/nginx/vim.conf.template @@ -1,6 +1,6 @@ upstream vim-app { # define the vim-app upstream server - server vim-app:8001 fail_timeout=0; + server localhost:8001 fail_timeout=0; } server { diff --git a/web-app/django/VIM/settings.py b/web-app/django/VIM/settings.py index b10fb252..0a13412a 100644 --- a/web-app/django/VIM/settings.py +++ b/web-app/django/VIM/settings.py @@ -116,7 +116,7 @@ "NAME": os.environ.get("POSTGRES_DB"), "USER": os.environ.get("POSTGRES_USER"), "PASSWORD": os.environ.get("POSTGRES_PASSWORD"), - "HOST": "vim-db", + "HOST": os.environ.get("POSTGRES_HOST", "vim-db"), "PORT": "5432", } } @@ -246,7 +246,7 @@ # SOLR SETTINGS -SOLR_URL = "http://solr:8983/solr/virtual-instrument-museum" +SOLR_URL = os.environ.get("SOLR_URL", "http://solr:8983/solr/virtual-instrument-museum") SOLR_TIMEOUT = 10 EMPTY_HBS_CATEGORY = "0" From ada4dcf183163a83216ec2e6a61d96d3ef569493 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Tue, 26 May 2026 23:24:13 -0400 Subject: [PATCH 19/21] Fix: postgres staging database --- k8s/staging/app/configmap.yaml | 4 ++-- k8s/staging/postgres/statefulset.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/k8s/staging/app/configmap.yaml b/k8s/staging/app/configmap.yaml index 193eedd0..9c02ab0d 100644 --- a/k8s/staging/app/configmap.yaml +++ b/k8s/staging/app/configmap.yaml @@ -11,6 +11,6 @@ data: EMAIL_HOST_USER: "AKIASGTLL5NYVV7JG65A" DEFAULT_FROM_EMAIL: "UMIL " POSTGRES_USER: "vim_admin" - POSTGRES_DB: "virtual_instrument_museum_staging" + POSTGRES_DB: "virtual_instrument_museum" POSTGRES_HOST: "vim-db-staging" - SOLR_URL: "http://solr-staging:8983/solr/virtual-instrument-museum" \ No newline at end of file + SOLR_URL: "http://solr-staging:8983/solr/virtual-instrument-museum" diff --git a/k8s/staging/postgres/statefulset.yaml b/k8s/staging/postgres/statefulset.yaml index 845abb6f..1345c303 100644 --- a/k8s/staging/postgres/statefulset.yaml +++ b/k8s/staging/postgres/statefulset.yaml @@ -28,7 +28,7 @@ spec: - containerPort: 5432 env: - name: POSTGRES_DB - value: virtual_instrument_museum_staging + value: virtual_instrument_museum - name: POSTGRES_USER value: vim_admin - name: POSTGRES_PASSWORD From a8acdf434636870d7b9fcae97cf338f50cdf8e0b Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Wed, 27 May 2026 11:09:13 -0400 Subject: [PATCH 20/21] Fix: use env var for host_app in nginx --- docker-compose-deployment.yml | 1 + docker-compose-test.yml | 1 + docker-compose.yml | 1 + k8s/production/app/deployment.yaml | 2 ++ k8s/staging/app/deployment.yaml | 2 ++ nginx/vim.conf.template | 2 +- 6 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docker-compose-deployment.yml b/docker-compose-deployment.yml index 96ab99fb..bbd44f97 100644 --- a/docker-compose-deployment.yml +++ b/docker-compose-deployment.yml @@ -51,6 +51,7 @@ services: restart: unless-stopped environment: - HOST_NAME=${HOST_NAME} + - APP_HOST=vim-app ports: - "8000:80" volumes: diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 73956f4e..71b8de00 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -60,6 +60,7 @@ services: restart: unless-stopped environment: - HOST_NAME=${HOST_NAME} + - APP_HOST=vim-app ports: - "8000:80" volumes: diff --git a/docker-compose.yml b/docker-compose.yml index 12658bc3..3e4fc32d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,6 +54,7 @@ services: restart: unless-stopped environment: - HOST_NAME=${HOST_NAME} + - APP_HOST=vim-app ports: - "8000:80" volumes: diff --git a/k8s/production/app/deployment.yaml b/k8s/production/app/deployment.yaml index 36b7c2e2..5a8d8d38 100644 --- a/k8s/production/app/deployment.yaml +++ b/k8s/production/app/deployment.yaml @@ -77,6 +77,8 @@ spec: configMapKeyRef: name: umil-config key: HOST_NAME + - name: APP_HOST + value: "localhost" volumeMounts: - name: static-files mountPath: /virtual-instrument-museum/static diff --git a/k8s/staging/app/deployment.yaml b/k8s/staging/app/deployment.yaml index cd67c6ee..9dfd266d 100644 --- a/k8s/staging/app/deployment.yaml +++ b/k8s/staging/app/deployment.yaml @@ -77,6 +77,8 @@ spec: configMapKeyRef: name: umil-config-staging key: HOST_NAME + - name: APP_HOST + value: "localhost" volumeMounts: - name: static-files mountPath: /virtual-instrument-museum/static diff --git a/nginx/vim.conf.template b/nginx/vim.conf.template index 8ae746c0..01b55d20 100644 --- a/nginx/vim.conf.template +++ b/nginx/vim.conf.template @@ -1,6 +1,6 @@ upstream vim-app { # define the vim-app upstream server - server localhost:8001 fail_timeout=0; + server ${APP_HOST}:8001 fail_timeout=0; } server { From 0c6a05441635a4ae3ce22618c09dbb8daa352f23 Mon Sep 17 00:00:00 2001 From: amirsadraabdollahi Date: Wed, 27 May 2026 11:20:50 -0400 Subject: [PATCH 21/21] Delete previous docker-compose for deploying --- docker-compose-deployment.yml | 77 ------------------------------ k8s/production/app/deployment.yaml | 6 +-- k8s/staging/app/deployment.yaml | 6 +-- 3 files changed, 6 insertions(+), 83 deletions(-) delete mode 100644 docker-compose-deployment.yml diff --git a/docker-compose-deployment.yml b/docker-compose-deployment.yml deleted file mode 100644 index bbd44f97..00000000 --- a/docker-compose-deployment.yml +++ /dev/null @@ -1,77 +0,0 @@ -# Uses Compose Specification (https://github.com/compose-spec/compose-spec/blob/master/spec.md) -services: - app: - build: - context: . - dockerfile: ./web-app/Dockerfile - target: prod - container_name: vim-app - volumes: - - ./web-app/django:/virtual-instrument-museum/vim-app - - vim-static:/virtual-instrument-museum/static - - vim-media:/virtual-instrument-museum/media - depends_on: - postgres: - condition: service_healthy - environment: - - MODE=prod - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - - HOST_NAME=${HOST_NAME} - - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} - - EMAIL_HOST=${EMAIL_HOST} - - EMAIL_PORT=${EMAIL_PORT} - - EMAIL_HOST_USER=${EMAIL_HOST_USER} - - EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD} - - DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL} - - postgres: - image: postgres:15.4 - container_name: vim-db - volumes: - - ./data/postgres:/var/lib/postgresql/data - environment: - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - healthcheck: - test: "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" - interval: 15s - timeout: 30s - retries: 5 - start_period: 30s - restart: unless-stopped - - nginx: - build: - context: . - dockerfile: ./nginx/Dockerfile.prod - container_name: vim-nginx - restart: unless-stopped - environment: - - HOST_NAME=${HOST_NAME} - - APP_HOST=vim-app - ports: - - "8000:80" - volumes: - - vim-static:/virtual-instrument-museum/static - - vim-media:/virtual-instrument-museum/media - depends_on: - - app - - solr: - build: ./solr - container_name: vim-solr - restart: unless-stopped - volumes: - - umil-solr:/var/solr - - ./solr/cores:/var/solr/data - -networks: - vim-net: - -volumes: - vim-static: - vim-media: - umil-solr: diff --git a/k8s/production/app/deployment.yaml b/k8s/production/app/deployment.yaml index 5a8d8d38..9bf05527 100644 --- a/k8s/production/app/deployment.yaml +++ b/k8s/production/app/deployment.yaml @@ -17,7 +17,7 @@ spec: - name: ghcr-pull-secret initContainers: - name: collectstatic - image: ghcr.io/ddmal/umil-app:k8s-migration-latest + image: ghcr.io/ddmal/umil-app:develop-latest workingDir: /virtual-instrument-museum/vim-app command: ["python", "manage.py", "collectstatic", "--noinput"] envFrom: @@ -30,7 +30,7 @@ spec: mountPath: /virtual-instrument-museum/static containers: - name: app - image: ghcr.io/ddmal/umil-app:k8s-migration-latest + image: ghcr.io/ddmal/umil-app:develop-latest imagePullPolicy: Always workingDir: /virtual-instrument-museum/vim-app command: ["gunicorn", "-w", "2", "-b", "0:8001", "--access-logfile", "-", "VIM.wsgi"] @@ -67,7 +67,7 @@ spec: cpu: "1" - name: nginx - image: ghcr.io/ddmal/umil-nginx:k8s-migration-latest + image: ghcr.io/ddmal/umil-nginx:develop-latest imagePullPolicy: Always ports: - containerPort: 80 diff --git a/k8s/staging/app/deployment.yaml b/k8s/staging/app/deployment.yaml index 9dfd266d..17992e88 100644 --- a/k8s/staging/app/deployment.yaml +++ b/k8s/staging/app/deployment.yaml @@ -17,7 +17,7 @@ spec: - name: ghcr-pull-secret initContainers: - name: collectstatic - image: ghcr.io/ddmal/umil-app:k8s-migration-latest + image: ghcr.io/ddmal/umil-app:develop-latest workingDir: /virtual-instrument-museum/vim-app command: ["python", "manage.py", "collectstatic", "--noinput"] envFrom: @@ -30,7 +30,7 @@ spec: mountPath: /virtual-instrument-museum/static containers: - name: app - image: ghcr.io/ddmal/umil-app:k8s-migration-latest + image: ghcr.io/ddmal/umil-app:develop-latest imagePullPolicy: Always workingDir: /virtual-instrument-museum/vim-app command: ["gunicorn", "-w", "2", "-b", "0:8001", "--access-logfile", "-", "VIM.wsgi"] @@ -67,7 +67,7 @@ spec: cpu: "1" - name: nginx - image: ghcr.io/ddmal/umil-nginx:k8s-migration-latest + image: ghcr.io/ddmal/umil-nginx:develop-latest imagePullPolicy: Always ports: - containerPort: 80