From 502ab7df2dbc7c3253202c3a57d55706a9df4a9f Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Thu, 16 Apr 2026 15:27:23 -0700 Subject: [PATCH 1/9] feat: remove redis --- README.md | 2 +- Tiltfile | 6 +- manifests/db/redis.yaml | 72 ------------------- manifests/migration.yaml | 2 - manifests/virtool/base/deployment.yaml | 2 - manifests/workflows/build-index.yaml | 5 -- manifests/workflows/create-sample.yaml | 5 -- manifests/workflows/create-subtraction.yaml | 5 -- manifests/workflows/iimi.yaml | 5 -- manifests/workflows/kustomization.yaml | 3 - manifests/workflows/nuvs.yaml | 5 -- manifests/workflows/patches/patch-json.yaml | 18 ----- .../workflows/patches/patch-trigger.yaml | 15 ---- manifests/workflows/pathoscope.yaml | 5 -- 14 files changed, 3 insertions(+), 147 deletions(-) delete mode 100644 manifests/db/redis.yaml delete mode 100644 manifests/workflows/patches/patch-trigger.yaml diff --git a/README.md b/README.md index 05a6385..e048f1e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ ``` Tilt manages the Kubernetes development environment. It starts all necessary services - (KEDA, MongoDB, PostgreSQL, Redis) and the Virtool workloads and services. + (KEDA, MongoDB, PostgreSQL) and the Virtool workloads and services. ## Tilt diff --git a/Tiltfile b/Tiltfile index 9eb04ce..11c99d9 100644 --- a/Tiltfile +++ b/Tiltfile @@ -27,7 +27,6 @@ helm_resource( k8s_yaml('manifests/db/mongo.yaml') k8s_yaml('manifests/db/postgres.yaml') -k8s_yaml('manifests/db/redis.yaml') k8s_yaml('manifests/storage.yaml') k8s_resource( @@ -40,7 +39,6 @@ k8s_resource( k8s_resource("mongo", labels=['data']) k8s_resource("postgres", labels=['data']) -k8s_resource('redis', labels=['data']) if 'migration' in to_edit: docker_build('ghcr.io/virtool/migration', '../virtool-migration/') @@ -102,7 +100,7 @@ k8s_resource( 'virtool-migration', labels=['virtool'], new_name="migration", - resource_deps=["mongo", "postgres", "redis", "storage"], + resource_deps=["mongo", "postgres", "storage"], trigger_mode=TRIGGER_MODE_MANUAL ) @@ -173,7 +171,7 @@ k8s_kind( k8s_yaml(kustomize('manifests/workflows')) -scaled_job_deps = ['keda', 'redis'] +scaled_job_deps = ['keda'] k8s_resource( "virtool-workflow-build-index", diff --git a/manifests/db/redis.yaml b/manifests/db/redis.yaml deleted file mode 100644 index 3972f30..0000000 --- a/manifests/db/redis.yaml +++ /dev/null @@ -1,72 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: redis - labels: - app: redis -spec: - serviceName: redis - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - image: redis:8 - name: redis - imagePullPolicy: Always - args: - - --requirepass - - virtool - resources: - requests: - cpu: 250m - memory: 256Mi - limits: - cpu: 375m - memory: 384Mi - readinessProbe: - exec: - command: - - redis-cli - - -a - - virtool - - ping - initialDelaySeconds: 5 - periodSeconds: 10 - livenessProbe: - exec: - command: - - redis-cli - - -a - - virtool - - ping - initialDelaySeconds: 10 - periodSeconds: 10 - volumeMounts: - - name: data - mountPath: /data - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: ["ReadWriteOnce"] - resources: - requests: - storage: 5Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: redis -spec: - selector: - app: redis - ports: - - protocol: TCP - port: 6379 - targetPort: 6379 diff --git a/manifests/migration.yaml b/manifests/migration.yaml index 74c978e..1c5b328 100644 --- a/manifests/migration.yaml +++ b/manifests/migration.yaml @@ -27,8 +27,6 @@ spec: value: "mongodb://virtool:virtool@mongo.default.svc.cluster.local/virtool" - name: VT_POSTGRES_CONNECTION_STRING value: "postgresql+asyncpg://virtool@postgres.default.svc.cluster.local?password=virtool" - - name: VT_REDIS_CONNECTION_STRING - value: "redis://:virtool@redis.default.svc.cluster.local" imagePullPolicy: Always resources: limits: diff --git a/manifests/virtool/base/deployment.yaml b/manifests/virtool/base/deployment.yaml index da836f8..7918a7f 100644 --- a/manifests/virtool/base/deployment.yaml +++ b/manifests/virtool/base/deployment.yaml @@ -21,8 +21,6 @@ spec: value: "0.0.0.0" - name: VT_MONGODB_CONNECTION_STRING value: "mongodb://virtool:virtool@mongo.default.svc.cluster.local/virtool" - - name: VT_REDIS_CONNECTION_STRING - value: "redis://:virtool@redis.default.svc.cluster.local" - name: VT_POSTGRES_CONNECTION_STRING value: "postgresql+asyncpg://virtool@postgres.default.svc.cluster.local?password=virtool" ports: diff --git a/manifests/workflows/build-index.yaml b/manifests/workflows/build-index.yaml index d2f2a7f..571ed14 100644 --- a/manifests/workflows/build-index.yaml +++ b/manifests/workflows/build-index.yaml @@ -20,8 +20,6 @@ spec: value: "2" - name: VT_MEM value: "2" - - name: VT_REDIS_LIST_NAME - value: "jobs_build_index" resources: limits: cpu: 3 @@ -29,6 +27,3 @@ spec: requests: cpu: 3 memory: 5Gi - triggers: - - metadata: - listName: "jobs_build_index" diff --git a/manifests/workflows/create-sample.yaml b/manifests/workflows/create-sample.yaml index c28516b..bf24ac2 100644 --- a/manifests/workflows/create-sample.yaml +++ b/manifests/workflows/create-sample.yaml @@ -20,8 +20,6 @@ spec: value: "2" - name: VT_MEM value: "4" - - name: VT_REDIS_LIST_NAME - value: "jobs_create_sample" resources: limits: cpu: 2700m @@ -29,6 +27,3 @@ spec: requests: cpu: 2700m memory: 5Gi - triggers: - - metadata: - listName: "jobs_create_sample" diff --git a/manifests/workflows/create-subtraction.yaml b/manifests/workflows/create-subtraction.yaml index b4ba2f3..0e902fd 100644 --- a/manifests/workflows/create-subtraction.yaml +++ b/manifests/workflows/create-subtraction.yaml @@ -20,8 +20,6 @@ spec: value: "2" - name: VT_MEM value: "2" - - name: VT_REDIS_LIST_NAME - value: "jobs_create_subtraction" resources: limits: cpu: "3" @@ -29,6 +27,3 @@ spec: requests: cpu: "3" memory: 5Gi - triggers: - - metadata: - listName: "jobs_create_subtraction" diff --git a/manifests/workflows/iimi.yaml b/manifests/workflows/iimi.yaml index 54e2d48..47649d1 100644 --- a/manifests/workflows/iimi.yaml +++ b/manifests/workflows/iimi.yaml @@ -20,8 +20,6 @@ spec: value: "4" - name: VT_PROC value: "10" - - name: VT_REDIS_LIST_NAME - value: "jobs_iimi" resources: limits: cpu: "5" @@ -29,6 +27,3 @@ spec: requests: cpu: "5" memory: 11Gi - triggers: - - metadata: - listName: "jobs_iimi" diff --git a/manifests/workflows/kustomization.yaml b/manifests/workflows/kustomization.yaml index d948a9e..cf828cc 100644 --- a/manifests/workflows/kustomization.yaml +++ b/manifests/workflows/kustomization.yaml @@ -14,9 +14,6 @@ patches: - path: patches/patch-json.yaml target: kind: ScaledJob - - path: patches/patch-trigger.yaml - target: - kind: ScaledJob resources: - build-index.yaml diff --git a/manifests/workflows/nuvs.yaml b/manifests/workflows/nuvs.yaml index 912df5b..8643ee7 100644 --- a/manifests/workflows/nuvs.yaml +++ b/manifests/workflows/nuvs.yaml @@ -17,8 +17,6 @@ spec: value: "4" - name: VT_MEM value: "10" - - name: VT_REDIS_LIST_NAME - value: "jobs_nuvs" resources: limits: cpu: "5" @@ -26,6 +24,3 @@ spec: requests: cpu: "4" memory: 10Gi - triggers: - - metadata: - listName: "jobs_nuvs" \ No newline at end of file diff --git a/manifests/workflows/patches/patch-json.yaml b/manifests/workflows/patches/patch-json.yaml index 8427a5e..107de52 100644 --- a/manifests/workflows/patches/patch-json.yaml +++ b/manifests/workflows/patches/patch-json.yaml @@ -8,24 +8,6 @@ name: VT_JOBS_API_CONNECTION_STRING value: http://virtool-api-jobs-service.default.svc.cluster.local -- op: add - path: /spec/jobTargetRef/template/spec/containers/0/env/-1 - value: - name: VT_REDIS_CONNECTION_STRING - value: redis://:virtool@redis.default.svc.cluster.local - -- op: add - path: /spec/jobTargetRef/template/spec/containers/0/env/-1 - value: - name: VT_REDIS_HOST - value: redis.default.svc.cluster.local:6379 - -- op: add - path: /spec/jobTargetRef/template/spec/containers/0/env/-1 - value: - name: VT_REDIS_PASSWORD - value: virtool - - op: add path: /spec/jobTargetRef/template/spec/containers/0/env/-1 value: diff --git a/manifests/workflows/patches/patch-trigger.yaml b/manifests/workflows/patches/patch-trigger.yaml deleted file mode 100644 index fee4b13..0000000 --- a/manifests/workflows/patches/patch-trigger.yaml +++ /dev/null @@ -1,15 +0,0 @@ -- op: add - path: /spec/triggers/0/type - value: redis - -- op: add - path: /spec/triggers/0/metadata/address - value: redis.default.svc.cluster.local:6379 - -- op: add - path: /spec/triggers/0/metadata/passwordFromEnv - value: VT_REDIS_PASSWORD - -- op: add - path: /spec/triggers/0/metadata/listLength - value: "1" diff --git a/manifests/workflows/pathoscope.yaml b/manifests/workflows/pathoscope.yaml index 7ad7bcf..d0fd4f1 100644 --- a/manifests/workflows/pathoscope.yaml +++ b/manifests/workflows/pathoscope.yaml @@ -17,8 +17,6 @@ spec: value: "8" - name: VT_PROC value: "4" - - name: VT_REDIS_LIST_NAME - value: "jobs_pathoscope_bowtie" resources: limits: cpu: "5" @@ -26,6 +24,3 @@ spec: requests: cpu: "5" memory: 10Gi - triggers: - - metadata: - listName: "jobs_pathoscope_bowtie" From 5a0422b6a97bde488678e621367c0d6c3ed128bc Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Thu, 16 Apr 2026 15:51:46 -0700 Subject: [PATCH 2/9] feat: scale workflows via jobs counts api Replace the removed redis-list KEDA triggers with per-workflow metrics-api triggers that poll /jobs/v2/counts on the web api and scale on the pending count for each workflow. Add api-web to scaled_job_deps in Tiltfile so ScaledJobs come up after the endpoint is reachable. --- Tiltfile | 2 +- manifests/workflows/build-index.yaml | 7 +++++++ manifests/workflows/create-sample.yaml | 7 +++++++ manifests/workflows/create-subtraction.yaml | 7 +++++++ manifests/workflows/iimi.yaml | 7 +++++++ manifests/workflows/nuvs.yaml | 7 +++++++ manifests/workflows/pathoscope.yaml | 7 +++++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Tiltfile b/Tiltfile index 11c99d9..bad7a2f 100644 --- a/Tiltfile +++ b/Tiltfile @@ -171,7 +171,7 @@ k8s_kind( k8s_yaml(kustomize('manifests/workflows')) -scaled_job_deps = ['keda'] +scaled_job_deps = ['keda', 'api-web'] k8s_resource( "virtool-workflow-build-index", diff --git a/manifests/workflows/build-index.yaml b/manifests/workflows/build-index.yaml index 571ed14..dc8c9ef 100644 --- a/manifests/workflows/build-index.yaml +++ b/manifests/workflows/build-index.yaml @@ -27,3 +27,10 @@ spec: requests: cpu: 3 memory: 5Gi + triggers: + - type: metrics-api + metadata: + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + valueLocation: "pending.build_index" + targetValue: "1" + activationTargetValue: "0" diff --git a/manifests/workflows/create-sample.yaml b/manifests/workflows/create-sample.yaml index bf24ac2..95024c3 100644 --- a/manifests/workflows/create-sample.yaml +++ b/manifests/workflows/create-sample.yaml @@ -27,3 +27,10 @@ spec: requests: cpu: 2700m memory: 5Gi + triggers: + - type: metrics-api + metadata: + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + valueLocation: "pending.create_sample" + targetValue: "1" + activationTargetValue: "0" diff --git a/manifests/workflows/create-subtraction.yaml b/manifests/workflows/create-subtraction.yaml index 0e902fd..399dafe 100644 --- a/manifests/workflows/create-subtraction.yaml +++ b/manifests/workflows/create-subtraction.yaml @@ -27,3 +27,10 @@ spec: requests: cpu: "3" memory: 5Gi + triggers: + - type: metrics-api + metadata: + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + valueLocation: "pending.create_subtraction" + targetValue: "1" + activationTargetValue: "0" diff --git a/manifests/workflows/iimi.yaml b/manifests/workflows/iimi.yaml index 47649d1..f00de29 100644 --- a/manifests/workflows/iimi.yaml +++ b/manifests/workflows/iimi.yaml @@ -27,3 +27,10 @@ spec: requests: cpu: "5" memory: 11Gi + triggers: + - type: metrics-api + metadata: + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + valueLocation: "pending.iimi" + targetValue: "1" + activationTargetValue: "0" diff --git a/manifests/workflows/nuvs.yaml b/manifests/workflows/nuvs.yaml index 8643ee7..a96085b 100644 --- a/manifests/workflows/nuvs.yaml +++ b/manifests/workflows/nuvs.yaml @@ -24,3 +24,10 @@ spec: requests: cpu: "4" memory: 10Gi + triggers: + - type: metrics-api + metadata: + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + valueLocation: "pending.nuvs" + targetValue: "1" + activationTargetValue: "0" diff --git a/manifests/workflows/pathoscope.yaml b/manifests/workflows/pathoscope.yaml index d0fd4f1..57747c2 100644 --- a/manifests/workflows/pathoscope.yaml +++ b/manifests/workflows/pathoscope.yaml @@ -24,3 +24,10 @@ spec: requests: cpu: "5" memory: 10Gi + triggers: + - type: metrics-api + metadata: + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + valueLocation: "pending.pathoscope" + targetValue: "1" + activationTargetValue: "0" From 74ec520c52cb980faa2e4ca643f1b5f3852e0aa6 Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Mon, 20 Apr 2026 19:49:01 -0700 Subject: [PATCH 3/9] feat: use garage Run the bucket/key bootstrap as a native sidecar inside the garage pod instead of a separate Job. Sidecar readiness gates pod readiness so dependents only proceed after bootstrap completes. --- Tiltfile | 4 +- manifests/db/garage.yaml | 169 ++++++++++++++++++++ manifests/migration.yaml | 2 +- manifests/ui/kustomization.yaml | 2 +- manifests/virtool/base/kustomization.yaml | 2 +- manifests/workflows/build-index.yaml | 6 +- manifests/workflows/create-sample.yaml | 6 +- manifests/workflows/create-subtraction.yaml | 6 +- manifests/workflows/iimi.yaml | 4 +- manifests/workflows/nuvs.yaml | 6 +- manifests/workflows/pathoscope.yaml | 6 +- 11 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 manifests/db/garage.yaml diff --git a/Tiltfile b/Tiltfile index bad7a2f..07819a9 100644 --- a/Tiltfile +++ b/Tiltfile @@ -25,6 +25,7 @@ helm_resource( resource_deps=['kedacore'] ) +k8s_yaml('manifests/db/garage.yaml') k8s_yaml('manifests/db/mongo.yaml') k8s_yaml('manifests/db/postgres.yaml') k8s_yaml('manifests/storage.yaml') @@ -37,6 +38,7 @@ k8s_resource( labels=['data'] ) +k8s_resource("garage", labels=['data']) k8s_resource("mongo", labels=['data']) k8s_resource("postgres", labels=['data']) @@ -100,7 +102,7 @@ k8s_resource( 'virtool-migration', labels=['virtool'], new_name="migration", - resource_deps=["mongo", "postgres", "storage"], + resource_deps=["garage", "mongo", "postgres", "storage"], trigger_mode=TRIGGER_MODE_MANUAL ) diff --git a/manifests/db/garage.yaml b/manifests/db/garage.yaml new file mode 100644 index 0000000..d829350 --- /dev/null +++ b/manifests/db/garage.yaml @@ -0,0 +1,169 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: garage-config +data: + garage.toml: | + metadata_dir = "/var/lib/garage/meta" + data_dir = "/var/lib/garage/data" + db_engine = "sqlite" + replication_factor = 1 + rpc_bind_addr = "[::]:3901" + rpc_public_addr = "garage:3901" + rpc_secret = "1111111111111111111111111111111111111111111111111111111111111111" + + [s3_api] + s3_region = "garage" + api_bind_addr = "[::]:3900" + root_domain = ".s3.garage" + + [admin] + api_bind_addr = "[::]:3903" + admin_token = "virtool-dev-admin-token" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: garage-init-script +data: + init.sh: | + #!/bin/sh + set -eu + + ADMIN_URL="http://garage:3903" + AUTH="Authorization: Bearer virtool-dev-admin-token" + BUCKET="virtool" + ACCESS_KEY_ID="GK000000000000000000000001" + SECRET_ACCESS_KEY="0000000000000000000000000000000000000000000000000000000000000001" + + apk add --no-cache curl jq >/dev/null + + until curl -sf -H "${AUTH}" "${ADMIN_URL}/health" >/dev/null; do sleep 1; done + + NODE_ID=$(curl -sf -H "${AUTH}" "${ADMIN_URL}/v1/status" | jq -r '.node') + CURRENT_VERSION=$(curl -sf -H "${AUTH}" "${ADMIN_URL}/v1/layout" | jq -r '.version') + + if [ "${CURRENT_VERSION}" = "0" ]; then + curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ + -d "[{\"id\":\"${NODE_ID}\",\"zone\":\"dc1\",\"capacity\":1000000000,\"tags\":[]}]" \ + "${ADMIN_URL}/v1/layout" >/dev/null + curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ + -d '{"version":1}' "${ADMIN_URL}/v1/layout/apply" >/dev/null + fi + + BUCKET_ID=$(curl -s -H "${AUTH}" "${ADMIN_URL}/v1/bucket?globalAlias=${BUCKET}" | jq -r '.id // empty') + if [ -z "${BUCKET_ID}" ]; then + BUCKET_ID=$(curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ + -d "{\"globalAlias\":\"${BUCKET}\"}" "${ADMIN_URL}/v1/bucket" | jq -r '.id') + fi + + EXISTING_KEY=$(curl -s -H "${AUTH}" "${ADMIN_URL}/v1/key?id=${ACCESS_KEY_ID}" | jq -r '.accessKeyId // empty') + if [ -z "${EXISTING_KEY}" ]; then + curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ + -d "{\"accessKeyId\":\"${ACCESS_KEY_ID}\",\"secretAccessKey\":\"${SECRET_ACCESS_KEY}\",\"name\":\"virtool-dev-key\"}" \ + "${ADMIN_URL}/v1/key/import" >/dev/null + fi + + curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ + -d "{\"bucketId\":\"${BUCKET_ID}\",\"accessKeyId\":\"${ACCESS_KEY_ID}\",\"permissions\":{\"read\":true,\"write\":true,\"owner\":false}}" \ + "${ADMIN_URL}/v1/bucket/allow" >/dev/null + + echo "garage bootstrap ok: bucket=${BUCKET} key=${ACCESS_KEY_ID}" +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: garage +spec: + serviceName: "garage" + replicas: 1 + selector: + matchLabels: + app: garage + template: + metadata: + labels: + app: garage + spec: + initContainers: + - name: garage-init + image: alpine:3.20 + restartPolicy: Always + command: ["/bin/sh", "-c", "/scripts/init.sh && touch /tmp/ready && exec sleep infinity"] + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + readinessProbe: + exec: + command: ["test", "-f", "/tmp/ready"] + periodSeconds: 2 + volumeMounts: + - name: init-script + mountPath: /scripts + containers: + - name: garage + image: dxflrs/garage:v1.1.0 + ports: + - containerPort: 3900 + - containerPort: 3903 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + readinessProbe: + httpGet: + path: /health + port: 3903 + initialDelaySeconds: 2 + periodSeconds: 5 + volumeMounts: + - name: config + mountPath: /etc/garage.toml + subPath: garage.toml + - name: meta + mountPath: /var/lib/garage/meta + - name: data + mountPath: /var/lib/garage/data + volumes: + - name: config + configMap: + name: garage-config + - name: init-script + configMap: + name: garage-init-script + defaultMode: 0755 + volumeClaimTemplates: + - metadata: + name: meta + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: garage +spec: + selector: + app: garage + ports: + - protocol: TCP + port: 3900 + targetPort: 3900 + clusterIP: None diff --git a/manifests/migration.yaml b/manifests/migration.yaml index 1c5b328..70ad541 100644 --- a/manifests/migration.yaml +++ b/manifests/migration.yaml @@ -13,7 +13,7 @@ spec: spec: restartPolicy: Never containers: - - image: ghcr.io/virtool/virtool:36.0.0 + - image: ghcr.io/virtool/virtool:36.15.1 name: virtool-migration command: ["virtool", "migration", "apply"] env: diff --git a/manifests/ui/kustomization.yaml b/manifests/ui/kustomization.yaml index f993652..90daec9 100644 --- a/manifests/ui/kustomization.yaml +++ b/manifests/ui/kustomization.yaml @@ -1,6 +1,6 @@ images: - name: ghcr.io/virtool/ui - newTag: 7.5.2 + newTag: 7.13.1 labels: - pairs: diff --git a/manifests/virtool/base/kustomization.yaml b/manifests/virtool/base/kustomization.yaml index 1c3b5d3..d5682dc 100644 --- a/manifests/virtool/base/kustomization.yaml +++ b/manifests/virtool/base/kustomization.yaml @@ -1,6 +1,6 @@ images: - name: ghcr.io/virtool/virtool - newTag: 36.0.0 + newTag: 36.20.0 labels: - pairs: diff --git a/manifests/workflows/build-index.yaml b/manifests/workflows/build-index.yaml index dc8c9ef..69a6a7e 100644 --- a/manifests/workflows/build-index.yaml +++ b/manifests/workflows/build-index.yaml @@ -14,12 +14,14 @@ spec: spec: containers: - name: workflow-build-index - image: ghcr.io/virtool/build-index:5.4.8 + image: ghcr.io/virtool/build-index:5.7.0 env: - name: VT_PROC value: "2" - name: VT_MEM value: "2" + - name: VT_WORKFLOW + value: build_index resources: limits: cpu: 3 @@ -30,7 +32,7 @@ spec: triggers: - type: metrics-api metadata: - url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/counts" valueLocation: "pending.build_index" targetValue: "1" activationTargetValue: "0" diff --git a/manifests/workflows/create-sample.yaml b/manifests/workflows/create-sample.yaml index 95024c3..cb84e16 100644 --- a/manifests/workflows/create-sample.yaml +++ b/manifests/workflows/create-sample.yaml @@ -14,12 +14,14 @@ spec: spec: containers: - name: workflow-create-sample - image: ghcr.io/virtool/create-sample:4.3.8 + image: ghcr.io/virtool/create-sample:4.6.0 env: - name: VT_PROC value: "2" - name: VT_MEM value: "4" + - name: VT_WORKFLOW + value: create_sample resources: limits: cpu: 2700m @@ -30,7 +32,7 @@ spec: triggers: - type: metrics-api metadata: - url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/counts" valueLocation: "pending.create_sample" targetValue: "1" activationTargetValue: "0" diff --git a/manifests/workflows/create-subtraction.yaml b/manifests/workflows/create-subtraction.yaml index 399dafe..30287dc 100644 --- a/manifests/workflows/create-subtraction.yaml +++ b/manifests/workflows/create-subtraction.yaml @@ -14,12 +14,14 @@ spec: spec: containers: - name: workflow-create-subtraction - image: ghcr.io/virtool/create-subtraction:5.3.8 + image: ghcr.io/virtool/create-subtraction:5.5.0 env: - name: VT_PROC value: "2" - name: VT_MEM value: "2" + - name: VT_WORKFLOW + value: create_subtraction resources: limits: cpu: "3" @@ -30,7 +32,7 @@ spec: triggers: - type: metrics-api metadata: - url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/counts" valueLocation: "pending.create_subtraction" targetValue: "1" activationTargetValue: "0" diff --git a/manifests/workflows/iimi.yaml b/manifests/workflows/iimi.yaml index f00de29..7e29c77 100644 --- a/manifests/workflows/iimi.yaml +++ b/manifests/workflows/iimi.yaml @@ -20,6 +20,8 @@ spec: value: "4" - name: VT_PROC value: "10" + - name: VT_WORKFLOW + value: iimi resources: limits: cpu: "5" @@ -30,7 +32,7 @@ spec: triggers: - type: metrics-api metadata: - url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/counts" valueLocation: "pending.iimi" targetValue: "1" activationTargetValue: "0" diff --git a/manifests/workflows/nuvs.yaml b/manifests/workflows/nuvs.yaml index a96085b..8bccf79 100644 --- a/manifests/workflows/nuvs.yaml +++ b/manifests/workflows/nuvs.yaml @@ -11,12 +11,14 @@ spec: spec: containers: - name: workflow-nuvs - image: ghcr.io/virtool/nuvs:5.4.5 + image: ghcr.io/virtool/nuvs:5.6.0 env: - name: VT_PROC value: "4" - name: VT_MEM value: "10" + - name: VT_WORKFLOW + value: nuvs resources: limits: cpu: "5" @@ -27,7 +29,7 @@ spec: triggers: - type: metrics-api metadata: - url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/counts" valueLocation: "pending.nuvs" targetValue: "1" activationTargetValue: "0" diff --git a/manifests/workflows/pathoscope.yaml b/manifests/workflows/pathoscope.yaml index 57747c2..172bd02 100644 --- a/manifests/workflows/pathoscope.yaml +++ b/manifests/workflows/pathoscope.yaml @@ -11,12 +11,14 @@ spec: spec: containers: - name: workflow-pathoscope - image: ghcr.io/virtool/pathoscope:5.9.4 + image: ghcr.io/virtool/pathoscope:5.11.0 env: - name: VT_MEM value: "8" - name: VT_PROC value: "4" + - name: VT_WORKFLOW + value: pathoscope resources: limits: cpu: "5" @@ -27,7 +29,7 @@ spec: triggers: - type: metrics-api metadata: - url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/v2/counts" + url: "http://virtool-api-web-service.default.svc.cluster.local/jobs/counts" valueLocation: "pending.pathoscope" targetValue: "1" activationTargetValue: "0" From f263bd98bfd59f73f6793117427c5c7251785f6b Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Thu, 30 Apr 2026 11:24:08 -0700 Subject: [PATCH 4/9] fix: update images --- Tiltfile | 8 ++++++ manifests/db/garage.yaml | 2 +- manifests/migration.yaml | 2 +- manifests/ui/kustomization.yaml | 2 +- manifests/virtool/base/kustomization.yaml | 2 +- manifests/workflows/nuvs.yaml | 2 +- manifests/workflows/pathoscope.yaml | 2 +- scripts/pull.sh | 31 ++++++++++++++++------- scripts/wipe.sh | 23 +++++++++++++++++ 9 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 scripts/wipe.sh diff --git a/Tiltfile b/Tiltfile index 07819a9..172b2a5 100644 --- a/Tiltfile +++ b/Tiltfile @@ -16,6 +16,14 @@ cmd_button('pull', text='Pull', ) +cmd_button('wipe', + argv=['bash', 'scripts/wipe.sh'], + icon_name="delete_forever", + location=location.NAV, + text='Wipe', + requires_confirmation=True, +) + helm_repo('kedacore', 'https://kedacore.github.io/charts', labels=['helm']) helm_resource( diff --git a/manifests/db/garage.yaml b/manifests/db/garage.yaml index d829350..cecd01b 100644 --- a/manifests/db/garage.yaml +++ b/manifests/db/garage.yaml @@ -30,7 +30,7 @@ data: #!/bin/sh set -eu - ADMIN_URL="http://garage:3903" + ADMIN_URL="http://127.0.0.1:3903" AUTH="Authorization: Bearer virtool-dev-admin-token" BUCKET="virtool" ACCESS_KEY_ID="GK000000000000000000000001" diff --git a/manifests/migration.yaml b/manifests/migration.yaml index 70ad541..e6aca7d 100644 --- a/manifests/migration.yaml +++ b/manifests/migration.yaml @@ -13,7 +13,7 @@ spec: spec: restartPolicy: Never containers: - - image: ghcr.io/virtool/virtool:36.15.1 + - image: ghcr.io/virtool/virtool:36.24.0 name: virtool-migration command: ["virtool", "migration", "apply"] env: diff --git a/manifests/ui/kustomization.yaml b/manifests/ui/kustomization.yaml index 90daec9..884d34a 100644 --- a/manifests/ui/kustomization.yaml +++ b/manifests/ui/kustomization.yaml @@ -1,6 +1,6 @@ images: - name: ghcr.io/virtool/ui - newTag: 7.13.1 + newTag: 7.13.3 labels: - pairs: diff --git a/manifests/virtool/base/kustomization.yaml b/manifests/virtool/base/kustomization.yaml index d5682dc..0975e22 100644 --- a/manifests/virtool/base/kustomization.yaml +++ b/manifests/virtool/base/kustomization.yaml @@ -1,6 +1,6 @@ images: - name: ghcr.io/virtool/virtool - newTag: 36.20.0 + newTag: 36.24.0 labels: - pairs: diff --git a/manifests/workflows/nuvs.yaml b/manifests/workflows/nuvs.yaml index 8bccf79..a93e15e 100644 --- a/manifests/workflows/nuvs.yaml +++ b/manifests/workflows/nuvs.yaml @@ -11,7 +11,7 @@ spec: spec: containers: - name: workflow-nuvs - image: ghcr.io/virtool/nuvs:5.6.0 + image: ghcr.io/virtool/nuvs:5.7.0 env: - name: VT_PROC value: "4" diff --git a/manifests/workflows/pathoscope.yaml b/manifests/workflows/pathoscope.yaml index 172bd02..d40dc49 100644 --- a/manifests/workflows/pathoscope.yaml +++ b/manifests/workflows/pathoscope.yaml @@ -11,7 +11,7 @@ spec: spec: containers: - name: workflow-pathoscope - image: ghcr.io/virtool/pathoscope:5.11.0 + image: ghcr.io/virtool/pathoscope:5.13.0 env: - name: VT_MEM value: "8" diff --git a/scripts/pull.sh b/scripts/pull.sh index ee13fca..2a0616d 100644 --- a/scripts/pull.sh +++ b/scripts/pull.sh @@ -6,18 +6,19 @@ if ! command -v jq &> /dev/null; then exit 1 fi -fetch_and_update() { +fetch_latest_tag() { local repo=$1 - local file=$2 - local prefix=$3 - - # Fetch the latest release tag local url="https://api.github.com/repos/${repo}/releases/latest" - local tag=$(curl -s "$url" | jq -r '.tag_name') + curl -s "$url" | jq -r '.tag_name' +} + +update_tag() { + local file=$1 + local prefix=$2 + local tag=$3 - # Update the file if it exists if [[ -f "$file" ]]; then - sed -i "s/${prefix}[0-9.]\+/${prefix}${tag}/" "$file" + sed -i "s|${prefix}[0-9.]\+|${prefix}${tag}|" "$file" echo "Using tag '$tag' for $file" else echo "Error: File $file not found." @@ -25,10 +26,22 @@ fetch_and_update() { fi } +fetch_and_update() { + local repo=$1 + local file=$2 + local prefix=$3 + local tag + tag=$(fetch_latest_tag "$repo") + update_tag "$file" "$prefix" "$tag" +} + echo "Server" echo "" -fetch_and_update "virtool/virtool" "manifests/virtool/base/kustomization.yaml" "newTag: " +virtool_tag=$(fetch_latest_tag "virtool/virtool") +update_tag "manifests/virtool/base/kustomization.yaml" "newTag: " "$virtool_tag" +update_tag "manifests/migration.yaml" "ghcr.io/virtool/virtool:" "$virtool_tag" + fetch_and_update "virtool/virtool-ui" "manifests/ui/kustomization.yaml" "newTag: " echo "" diff --git a/scripts/wipe.sh b/scripts/wipe.sh new file mode 100644 index 0000000..9a8b3fc --- /dev/null +++ b/scripts/wipe.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +echo "Deleting StatefulSets..." +kubectl delete statefulset garage mongo postgres --ignore-not-found --wait=false + +echo "Deleting PVCs..." +kubectl delete pvc \ + data-garage-0 meta-garage-0 \ + data-mongo-0 \ + data-postgres-0 \ + pvc-virtool \ + --ignore-not-found --wait=false + +echo "Deleting PV..." +kubectl delete pv pv-virtool --ignore-not-found --wait=false + +echo "Clearing host data at /virtool/data..." +if [ -d /virtool/data ]; then + rm -rf /virtool/data/* 2>/dev/null || sudo rm -rf /virtool/data/* +fi + +echo "Done. Tilt will recreate resources on next trigger." From 9efbcd99e9417c82652d9788575fe836ea5f91d2 Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Thu, 30 Apr 2026 11:28:42 -0700 Subject: [PATCH 5/9] docs: add AGENTS.md and symlink CLAUDE.md to it --- AGENTS.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 69 insertions(+) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..84ed1b6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,68 @@ +# Dev + +This repository manages the local Kubernetes development environment for Virtool using Tilt and Minikube. + +## Stack + +- **Tilt** — orchestrates the dev cluster; the `Tiltfile` is the main entry point +- **Minikube** — local Kubernetes cluster +- **KEDA** — scales workflow jobs via a `metrics-api` trigger pointing at `http://virtool-api-web-service.../jobs/counts` +- **MongoDB + PostgreSQL** — persisted across `tilt up`/`tilt down` runs via PVCs +- **Garage** — self-hosted S3 store (replaces Redis); bootstrapped by an Alpine init-container that creates the `virtool` bucket and imports a dev access key + +## Layout + +``` +manifests/ Kustomize manifests for all cluster resources + db/ MongoDB, PostgreSQL, Garage + ingress.yaml + migration.yaml + storage.yaml + ui/ + virtool/ + workflows/ ScaledJob manifests for each workflow +scripts/ + init.sh Create/reset the Minikube cluster + hosts.sh Write cluster IP to /etc/hosts + pull.sh Update image tags in kustomization + migration manifests + wipe.sh Nuke PVCs, PVs, and host data +Tiltfile Tilt configuration (resources, buttons, live-edit flags) +``` + +## Common tasks + +**Start the cluster** +```shell +bash scripts/init.sh # first time or full reset +tilt up +``` + +**Live-edit a service** (repo must be a sibling of this directory) +```shell +tilt up -- --to-edit backend # virtool/virtool +tilt up -- --to-edit ui # virtool/virtool-ui +tilt up -- --to-edit +``` + +Workflow names: `build-index`, `create-sample`, `create-subtraction`, `iimi`, `pathoscope`, `nuvs` + +**Update images** +```shell +bash scripts/pull.sh +``` +Or click the **Pull** button in the Tilt UI. + +**Wipe persistent data** +```shell +bash scripts/wipe.sh +``` +Or click the **Wipe** button in the Tilt UI (requires confirmation). + +**Update `/etc/hosts`** (needed once per cluster recreation) +```shell +bash scripts/hosts.sh +``` + +## PR guidelines + +- No test plan section in PR descriptions. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 6ea1f212a55843867a443eefed04202208b7ee0f Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Fri, 1 May 2026 09:20:34 -0700 Subject: [PATCH 6/9] chore: harden dev scripts and refactor garage init - scripts/wipe.sh: refuse to run unless kubectl current-context and the running Minikube profile are both 'virtool'; replace `sudo rm -rf /virtool/data/*` with `find -mindepth 1` so dotfiles are removed too. - scripts/init.sh: pin every minikube/kubectl call to the dedicated 'virtool' profile/context. - scripts/pull.sh: strip leading 'v' from upstream tags and clean up the sed backup file. - manifests/db/garage.yaml: move garage-init from an initContainer running apk-installed curl/jq to a sidecar using the badouralix/curl-jq image. --- manifests/db/garage.yaml | 34 +++++++++++++--------------------- scripts/init.sh | 22 ++++++++++++---------- scripts/pull.sh | 4 ++-- scripts/wipe.sh | 32 +++++++++++++++++++++++++------- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/manifests/db/garage.yaml b/manifests/db/garage.yaml index cecd01b..76fed28 100644 --- a/manifests/db/garage.yaml +++ b/manifests/db/garage.yaml @@ -36,8 +36,6 @@ data: ACCESS_KEY_ID="GK000000000000000000000001" SECRET_ACCESS_KEY="0000000000000000000000000000000000000000000000000000000000000001" - apk add --no-cache curl jq >/dev/null - until curl -sf -H "${AUTH}" "${ADMIN_URL}/health" >/dev/null; do sleep 1; done NODE_ID=$(curl -sf -H "${AUTH}" "${ADMIN_URL}/v1/status" | jq -r '.node') @@ -85,25 +83,6 @@ spec: labels: app: garage spec: - initContainers: - - name: garage-init - image: alpine:3.20 - restartPolicy: Always - command: ["/bin/sh", "-c", "/scripts/init.sh && touch /tmp/ready && exec sleep infinity"] - resources: - requests: - cpu: 50m - memory: 32Mi - limits: - cpu: 100m - memory: 64Mi - readinessProbe: - exec: - command: ["test", "-f", "/tmp/ready"] - periodSeconds: 2 - volumeMounts: - - name: init-script - mountPath: /scripts containers: - name: garage image: dxflrs/garage:v1.1.0 @@ -131,6 +110,19 @@ spec: mountPath: /var/lib/garage/meta - name: data mountPath: /var/lib/garage/data + - name: garage-init + image: badouralix/curl-jq + command: ["/bin/sh", "-c", "/scripts/init.sh && exec sleep infinity"] + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + volumeMounts: + - name: init-script + mountPath: /scripts volumes: - name: config configMap: diff --git a/scripts/init.sh b/scripts/init.sh index 6576874..aa90ca4 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -2,24 +2,26 @@ set -e -echo "Deleting any existing Minikube cluster..." -minikube delete --purge=true +PROFILE="virtool" + +echo "Deleting any existing Minikube cluster for profile '$PROFILE'..." +minikube -p "$PROFILE" delete --purge=true echo "Starting Minikube for first time..." -minikube start --cpus 8 --memory 16000 +minikube -p "$PROFILE" start --cpus 8 --memory 16000 echo "Enabling Minikube addons..." -minikube addons enable metrics-server +minikube -p "$PROFILE" addons enable metrics-server echo "Generating TLS certificate for virtool.local..." CERT_DIR=$(mktemp -d) mkcert -key-file "$CERT_DIR/key.pem" -cert-file "$CERT_DIR/cert.pem" virtool.local echo "Creating TLS secrets..." -kubectl -n kube-system create secret tls mkcert \ +kubectl --context "$PROFILE" -n kube-system create secret tls mkcert \ --key "$CERT_DIR/key.pem" \ --cert "$CERT_DIR/cert.pem" -kubectl create secret tls mkcert \ +kubectl --context "$PROFILE" create secret tls mkcert \ --key "$CERT_DIR/key.pem" \ --cert "$CERT_DIR/cert.pem" @@ -27,13 +29,13 @@ echo "Cleaning up temporary certificate files..." rm -rf "$CERT_DIR" echo "Configuring ingress addon to use custom certificate..." -minikube addons configure ingress <<< "kube-system/mkcert" -minikube addons enable ingress +minikube -p "$PROFILE" addons configure ingress <<< "kube-system/mkcert" +minikube -p "$PROFILE" addons enable ingress echo "Verifying ingress configuration..." -kubectl -n ingress-nginx get deployment ingress-nginx-controller -o yaml | grep "kube-system" +kubectl --context "$PROFILE" -n ingress-nginx get deployment ingress-nginx-controller -o yaml | grep "kube-system" echo "Configuring /etc/hosts..." -MINIKUBE_IP=$(minikube ip) +MINIKUBE_IP=$(minikube -p "$PROFILE" ip) sudo sed -i '/virtool.local/d' /etc/hosts echo -e "$MINIKUBE_IP\tvirtool.local" | sudo tee -a /etc/hosts diff --git a/scripts/pull.sh b/scripts/pull.sh index 2a0616d..4f483c5 100644 --- a/scripts/pull.sh +++ b/scripts/pull.sh @@ -9,7 +9,7 @@ fi fetch_latest_tag() { local repo=$1 local url="https://api.github.com/repos/${repo}/releases/latest" - curl -s "$url" | jq -r '.tag_name' + curl -s "$url" | jq -r '.tag_name' | sed 's/^v//' } update_tag() { @@ -18,7 +18,7 @@ update_tag() { local tag=$3 if [[ -f "$file" ]]; then - sed -i "s|${prefix}[0-9.]\+|${prefix}${tag}|" "$file" + sed -i.bak "s|${prefix}[0-9.]\+|${prefix}${tag}|" "$file" && rm "${file}.bak" echo "Using tag '$tag' for $file" else echo "Error: File $file not found." diff --git a/scripts/wipe.sh b/scripts/wipe.sh index 9a8b3fc..3dce1a3 100644 --- a/scripts/wipe.sh +++ b/scripts/wipe.sh @@ -1,8 +1,28 @@ #!/bin/bash set -e +PROFILE="virtool" +DATA_DIR="/virtool/data" + +current_context=$(kubectl config current-context 2>/dev/null || true) +if [[ "$current_context" != "$PROFILE" ]]; then + echo "Refusing to wipe: kubectl current-context is '${current_context:-}', expected '$PROFILE'." + echo "Switch contexts with: kubectl config use-context $PROFILE" + exit 1 +fi + +if ! minikube -p "$PROFILE" status >/dev/null 2>&1; then + echo "Refusing to wipe: Minikube profile '$PROFILE' is not running." + exit 1 +fi + +clear_data_dir() { + minikube -p "$PROFILE" ssh -- \ + "test -d $DATA_DIR && sudo find $DATA_DIR -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +" +} + echo "Deleting StatefulSets..." -kubectl delete statefulset garage mongo postgres --ignore-not-found --wait=false +kubectl delete statefulset garage mongo postgres --ignore-not-found echo "Deleting PVCs..." kubectl delete pvc \ @@ -10,14 +30,12 @@ kubectl delete pvc \ data-mongo-0 \ data-postgres-0 \ pvc-virtool \ - --ignore-not-found --wait=false + --ignore-not-found echo "Deleting PV..." -kubectl delete pv pv-virtool --ignore-not-found --wait=false +kubectl delete pv pv-virtool --ignore-not-found -echo "Clearing host data at /virtool/data..." -if [ -d /virtool/data ]; then - rm -rf /virtool/data/* 2>/dev/null || sudo rm -rf /virtool/data/* -fi +echo "Clearing contents of $DATA_DIR inside Minikube profile '$PROFILE'..." +clear_data_dir echo "Done. Tilt will recreate resources on next trigger." From 87baa40dc7d1b0a01cb4d9662e328cc46e043833 Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Fri, 1 May 2026 09:50:47 -0700 Subject: [PATCH 7/9] feat: replace garage with azurite Swap the Garage S3 store for Azurite (the Azure Blob Storage emulator) so the dev cluster matches the SDK and semantics used in production. Drops the Garage StatefulSet, init sidecar, and admin/layout/key bootstrap dance in favor of a single Azurite container with a /data PVC. Updates Tiltfile, wipe.sh, and AGENTS.md to reference azurite. App-side env wiring (connection string for devstoreaccount1) is a follow-up in the virtool repo; the deployments in this repo do not currently set any S3/Blob env vars. --- AGENTS.md | 4 +- Tiltfile | 6 +- manifests/db/azurite.yaml | 86 ++++++++++++++++++++ manifests/db/garage.yaml | 161 -------------------------------------- scripts/wipe.sh | 4 +- 5 files changed, 93 insertions(+), 168 deletions(-) create mode 100644 manifests/db/azurite.yaml delete mode 100644 manifests/db/garage.yaml diff --git a/AGENTS.md b/AGENTS.md index 84ed1b6..1ee28f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,13 +8,13 @@ This repository manages the local Kubernetes development environment for Virtool - **Minikube** — local Kubernetes cluster - **KEDA** — scales workflow jobs via a `metrics-api` trigger pointing at `http://virtool-api-web-service.../jobs/counts` - **MongoDB + PostgreSQL** — persisted across `tilt up`/`tilt down` runs via PVCs -- **Garage** — self-hosted S3 store (replaces Redis); bootstrapped by an Alpine init-container that creates the `virtool` bucket and imports a dev access key +- **Azurite** — Azure Blob Storage emulator; uses the well-known dev account `devstoreaccount1` with key `Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==` and persists blobs at `/data` via a PVC ## Layout ``` manifests/ Kustomize manifests for all cluster resources - db/ MongoDB, PostgreSQL, Garage + db/ MongoDB, PostgreSQL, Azurite ingress.yaml migration.yaml storage.yaml diff --git a/Tiltfile b/Tiltfile index 172b2a5..eaa7691 100644 --- a/Tiltfile +++ b/Tiltfile @@ -33,7 +33,7 @@ helm_resource( resource_deps=['kedacore'] ) -k8s_yaml('manifests/db/garage.yaml') +k8s_yaml('manifests/db/azurite.yaml') k8s_yaml('manifests/db/mongo.yaml') k8s_yaml('manifests/db/postgres.yaml') k8s_yaml('manifests/storage.yaml') @@ -46,7 +46,7 @@ k8s_resource( labels=['data'] ) -k8s_resource("garage", labels=['data']) +k8s_resource("azurite", labels=['data']) k8s_resource("mongo", labels=['data']) k8s_resource("postgres", labels=['data']) @@ -110,7 +110,7 @@ k8s_resource( 'virtool-migration', labels=['virtool'], new_name="migration", - resource_deps=["garage", "mongo", "postgres", "storage"], + resource_deps=["azurite", "mongo", "postgres", "storage"], trigger_mode=TRIGGER_MODE_MANUAL ) diff --git a/manifests/db/azurite.yaml b/manifests/db/azurite.yaml new file mode 100644 index 0000000..fde7fde --- /dev/null +++ b/manifests/db/azurite.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: azurite + labels: + app: azurite +spec: + serviceName: azurite + replicas: 1 + selector: + matchLabels: + app: azurite + template: + metadata: + labels: + app: azurite + spec: + containers: + - name: azurite + image: mcr.microsoft.com/azure-storage/azurite + args: + - azurite + - --blobHost + - 0.0.0.0 + - --queueHost + - 0.0.0.0 + - --tableHost + - 0.0.0.0 + - --location + - /data + ports: + - containerPort: 10000 + name: blob + - containerPort: 10001 + name: queue + - containerPort: 10002 + name: table + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + readinessProbe: + tcpSocket: + port: 10000 + initialDelaySeconds: 2 + periodSeconds: 5 + livenessProbe: + tcpSocket: + port: 10000 + initialDelaySeconds: 10 + periodSeconds: 10 + volumeMounts: + - name: data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: azurite +spec: + selector: + app: azurite + ports: + - name: blob + protocol: TCP + port: 10000 + targetPort: 10000 + - name: queue + protocol: TCP + port: 10001 + targetPort: 10001 + - name: table + protocol: TCP + port: 10002 + targetPort: 10002 diff --git a/manifests/db/garage.yaml b/manifests/db/garage.yaml deleted file mode 100644 index 76fed28..0000000 --- a/manifests/db/garage.yaml +++ /dev/null @@ -1,161 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: garage-config -data: - garage.toml: | - metadata_dir = "/var/lib/garage/meta" - data_dir = "/var/lib/garage/data" - db_engine = "sqlite" - replication_factor = 1 - rpc_bind_addr = "[::]:3901" - rpc_public_addr = "garage:3901" - rpc_secret = "1111111111111111111111111111111111111111111111111111111111111111" - - [s3_api] - s3_region = "garage" - api_bind_addr = "[::]:3900" - root_domain = ".s3.garage" - - [admin] - api_bind_addr = "[::]:3903" - admin_token = "virtool-dev-admin-token" ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: garage-init-script -data: - init.sh: | - #!/bin/sh - set -eu - - ADMIN_URL="http://127.0.0.1:3903" - AUTH="Authorization: Bearer virtool-dev-admin-token" - BUCKET="virtool" - ACCESS_KEY_ID="GK000000000000000000000001" - SECRET_ACCESS_KEY="0000000000000000000000000000000000000000000000000000000000000001" - - until curl -sf -H "${AUTH}" "${ADMIN_URL}/health" >/dev/null; do sleep 1; done - - NODE_ID=$(curl -sf -H "${AUTH}" "${ADMIN_URL}/v1/status" | jq -r '.node') - CURRENT_VERSION=$(curl -sf -H "${AUTH}" "${ADMIN_URL}/v1/layout" | jq -r '.version') - - if [ "${CURRENT_VERSION}" = "0" ]; then - curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ - -d "[{\"id\":\"${NODE_ID}\",\"zone\":\"dc1\",\"capacity\":1000000000,\"tags\":[]}]" \ - "${ADMIN_URL}/v1/layout" >/dev/null - curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ - -d '{"version":1}' "${ADMIN_URL}/v1/layout/apply" >/dev/null - fi - - BUCKET_ID=$(curl -s -H "${AUTH}" "${ADMIN_URL}/v1/bucket?globalAlias=${BUCKET}" | jq -r '.id // empty') - if [ -z "${BUCKET_ID}" ]; then - BUCKET_ID=$(curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ - -d "{\"globalAlias\":\"${BUCKET}\"}" "${ADMIN_URL}/v1/bucket" | jq -r '.id') - fi - - EXISTING_KEY=$(curl -s -H "${AUTH}" "${ADMIN_URL}/v1/key?id=${ACCESS_KEY_ID}" | jq -r '.accessKeyId // empty') - if [ -z "${EXISTING_KEY}" ]; then - curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ - -d "{\"accessKeyId\":\"${ACCESS_KEY_ID}\",\"secretAccessKey\":\"${SECRET_ACCESS_KEY}\",\"name\":\"virtool-dev-key\"}" \ - "${ADMIN_URL}/v1/key/import" >/dev/null - fi - - curl -sf -X POST -H "${AUTH}" -H "Content-Type: application/json" \ - -d "{\"bucketId\":\"${BUCKET_ID}\",\"accessKeyId\":\"${ACCESS_KEY_ID}\",\"permissions\":{\"read\":true,\"write\":true,\"owner\":false}}" \ - "${ADMIN_URL}/v1/bucket/allow" >/dev/null - - echo "garage bootstrap ok: bucket=${BUCKET} key=${ACCESS_KEY_ID}" ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: garage -spec: - serviceName: "garage" - replicas: 1 - selector: - matchLabels: - app: garage - template: - metadata: - labels: - app: garage - spec: - containers: - - name: garage - image: dxflrs/garage:v1.1.0 - ports: - - containerPort: 3900 - - containerPort: 3903 - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 250m - memory: 256Mi - readinessProbe: - httpGet: - path: /health - port: 3903 - initialDelaySeconds: 2 - periodSeconds: 5 - volumeMounts: - - name: config - mountPath: /etc/garage.toml - subPath: garage.toml - - name: meta - mountPath: /var/lib/garage/meta - - name: data - mountPath: /var/lib/garage/data - - name: garage-init - image: badouralix/curl-jq - command: ["/bin/sh", "-c", "/scripts/init.sh && exec sleep infinity"] - resources: - requests: - cpu: 50m - memory: 32Mi - limits: - cpu: 100m - memory: 64Mi - volumeMounts: - - name: init-script - mountPath: /scripts - volumes: - - name: config - configMap: - name: garage-config - - name: init-script - configMap: - name: garage-init-script - defaultMode: 0755 - volumeClaimTemplates: - - metadata: - name: meta - spec: - accessModes: ["ReadWriteOnce"] - resources: - requests: - storage: 1Gi - - metadata: - name: data - spec: - accessModes: ["ReadWriteOnce"] - resources: - requests: - storage: 5Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: garage -spec: - selector: - app: garage - ports: - - protocol: TCP - port: 3900 - targetPort: 3900 - clusterIP: None diff --git a/scripts/wipe.sh b/scripts/wipe.sh index 3dce1a3..6b7d6e6 100644 --- a/scripts/wipe.sh +++ b/scripts/wipe.sh @@ -22,11 +22,11 @@ clear_data_dir() { } echo "Deleting StatefulSets..." -kubectl delete statefulset garage mongo postgres --ignore-not-found +kubectl delete statefulset azurite mongo postgres --ignore-not-found echo "Deleting PVCs..." kubectl delete pvc \ - data-garage-0 meta-garage-0 \ + data-azurite-0 \ data-mongo-0 \ data-postgres-0 \ pvc-virtool \ From f518c8d5ac9336a41c044fd8a703e42e747d2d49 Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Fri, 1 May 2026 12:02:37 -0700 Subject: [PATCH 8/9] fix: fix azurite configuration --- manifests/db/azurite.yaml | 28 ++++++++++++++++++++++++++ manifests/virtool/base/deployment.yaml | 10 +++++++++ 2 files changed, 38 insertions(+) diff --git a/manifests/db/azurite.yaml b/manifests/db/azurite.yaml index fde7fde..70bb02c 100644 --- a/manifests/db/azurite.yaml +++ b/manifests/db/azurite.yaml @@ -15,6 +15,32 @@ spec: labels: app: azurite spec: + initContainers: + - name: azurite-init + image: mcr.microsoft.com/azure-cli:latest + restartPolicy: Always + env: + - name: AZURE_STORAGE_CONNECTION_STRING + value: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" + command: + - /bin/sh + - -c + - | + set -eu + until az storage container create --name virtool --output none 2>/dev/null; do sleep 2; done + touch /tmp/ready + exec sleep infinity + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + readinessProbe: + exec: + command: ["test", "-f", "/tmp/ready"] + periodSeconds: 2 containers: - name: azurite image: mcr.microsoft.com/azure-storage/azurite @@ -28,6 +54,8 @@ spec: - 0.0.0.0 - --location - /data + - --skipApiVersionCheck + - --disableProductStyleUrl ports: - containerPort: 10000 name: blob diff --git a/manifests/virtool/base/deployment.yaml b/manifests/virtool/base/deployment.yaml index 7918a7f..03e288c 100644 --- a/manifests/virtool/base/deployment.yaml +++ b/manifests/virtool/base/deployment.yaml @@ -23,6 +23,16 @@ spec: value: "mongodb://virtool:virtool@mongo.default.svc.cluster.local/virtool" - name: VT_POSTGRES_CONNECTION_STRING value: "postgresql+asyncpg://virtool@postgres.default.svc.cluster.local?password=virtool" + - name: VT_STORAGE_BACKEND + value: "azure" + - name: VT_STORAGE_AZURE_ACCOUNT + value: "devstoreaccount1" + - name: VT_STORAGE_AZURE_CONTAINER + value: "virtool" + - name: VT_STORAGE_AZURE_ACCESS_KEY + value: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + - name: VT_STORAGE_AZURE_ENDPOINT + value: "http://azurite.default.svc.cluster.local:10000/devstoreaccount1" ports: - containerPort: 9950 protocol: TCP From 8dd02086d40f99e50bd6ab3ceb5f39e959dad6e8 Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Wed, 6 May 2026 09:02:27 -0700 Subject: [PATCH 9/9] fix: fix init and wipe scripts --- manifests/migration.yaml | 2 +- manifests/virtool/base/kustomization.yaml | 2 +- manifests/workflows/iimi.yaml | 2 +- scripts/init.sh | 22 ++++++++++------------ scripts/wipe.sh | 15 +++++++-------- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/manifests/migration.yaml b/manifests/migration.yaml index e6aca7d..67c8044 100644 --- a/manifests/migration.yaml +++ b/manifests/migration.yaml @@ -13,7 +13,7 @@ spec: spec: restartPolicy: Never containers: - - image: ghcr.io/virtool/virtool:36.24.0 + - image: ghcr.io/virtool/virtool:36.25.3 name: virtool-migration command: ["virtool", "migration", "apply"] env: diff --git a/manifests/virtool/base/kustomization.yaml b/manifests/virtool/base/kustomization.yaml index 0975e22..55ef2df 100644 --- a/manifests/virtool/base/kustomization.yaml +++ b/manifests/virtool/base/kustomization.yaml @@ -1,6 +1,6 @@ images: - name: ghcr.io/virtool/virtool - newTag: 36.24.0 + newTag: 36.25.3 labels: - pairs: diff --git a/manifests/workflows/iimi.yaml b/manifests/workflows/iimi.yaml index 7e29c77..c698e52 100644 --- a/manifests/workflows/iimi.yaml +++ b/manifests/workflows/iimi.yaml @@ -14,7 +14,7 @@ spec: spec: containers: - name: virtool-workflow - image: ghcr.io/virtool/iimi:0.4.5 + image: ghcr.io/virtool/iimi:0.5.0 env: - name: VT_MEM value: "4" diff --git a/scripts/init.sh b/scripts/init.sh index aa90ca4..6576874 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -2,26 +2,24 @@ set -e -PROFILE="virtool" - -echo "Deleting any existing Minikube cluster for profile '$PROFILE'..." -minikube -p "$PROFILE" delete --purge=true +echo "Deleting any existing Minikube cluster..." +minikube delete --purge=true echo "Starting Minikube for first time..." -minikube -p "$PROFILE" start --cpus 8 --memory 16000 +minikube start --cpus 8 --memory 16000 echo "Enabling Minikube addons..." -minikube -p "$PROFILE" addons enable metrics-server +minikube addons enable metrics-server echo "Generating TLS certificate for virtool.local..." CERT_DIR=$(mktemp -d) mkcert -key-file "$CERT_DIR/key.pem" -cert-file "$CERT_DIR/cert.pem" virtool.local echo "Creating TLS secrets..." -kubectl --context "$PROFILE" -n kube-system create secret tls mkcert \ +kubectl -n kube-system create secret tls mkcert \ --key "$CERT_DIR/key.pem" \ --cert "$CERT_DIR/cert.pem" -kubectl --context "$PROFILE" create secret tls mkcert \ +kubectl create secret tls mkcert \ --key "$CERT_DIR/key.pem" \ --cert "$CERT_DIR/cert.pem" @@ -29,13 +27,13 @@ echo "Cleaning up temporary certificate files..." rm -rf "$CERT_DIR" echo "Configuring ingress addon to use custom certificate..." -minikube -p "$PROFILE" addons configure ingress <<< "kube-system/mkcert" -minikube -p "$PROFILE" addons enable ingress +minikube addons configure ingress <<< "kube-system/mkcert" +minikube addons enable ingress echo "Verifying ingress configuration..." -kubectl --context "$PROFILE" -n ingress-nginx get deployment ingress-nginx-controller -o yaml | grep "kube-system" +kubectl -n ingress-nginx get deployment ingress-nginx-controller -o yaml | grep "kube-system" echo "Configuring /etc/hosts..." -MINIKUBE_IP=$(minikube -p "$PROFILE" ip) +MINIKUBE_IP=$(minikube ip) sudo sed -i '/virtool.local/d' /etc/hosts echo -e "$MINIKUBE_IP\tvirtool.local" | sudo tee -a /etc/hosts diff --git a/scripts/wipe.sh b/scripts/wipe.sh index 6b7d6e6..0f67014 100644 --- a/scripts/wipe.sh +++ b/scripts/wipe.sh @@ -1,23 +1,22 @@ #!/bin/bash set -e -PROFILE="virtool" DATA_DIR="/virtool/data" current_context=$(kubectl config current-context 2>/dev/null || true) -if [[ "$current_context" != "$PROFILE" ]]; then - echo "Refusing to wipe: kubectl current-context is '${current_context:-}', expected '$PROFILE'." - echo "Switch contexts with: kubectl config use-context $PROFILE" +if [[ "$current_context" != "minikube" ]]; then + echo "Refusing to wipe: kubectl current-context is '${current_context:-}', expected 'minikube'." + echo "Switch contexts with: kubectl config use-context minikube" exit 1 fi -if ! minikube -p "$PROFILE" status >/dev/null 2>&1; then - echo "Refusing to wipe: Minikube profile '$PROFILE' is not running." +if ! minikube status >/dev/null 2>&1; then + echo "Refusing to wipe: Minikube is not running." exit 1 fi clear_data_dir() { - minikube -p "$PROFILE" ssh -- \ + minikube ssh -- \ "test -d $DATA_DIR && sudo find $DATA_DIR -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +" } @@ -35,7 +34,7 @@ kubectl delete pvc \ echo "Deleting PV..." kubectl delete pv pv-virtool --ignore-not-found -echo "Clearing contents of $DATA_DIR inside Minikube profile '$PROFILE'..." +echo "Clearing contents of $DATA_DIR..." clear_data_dir echo "Done. Tilt will recreate resources on next trigger."