Initial release: rarelens platform skeleton (AGPL-3.0)
ci / api (push) Failing after 10s
ci / terraform (push) Failing after 11s
ci / web (push) Failing after 35s
ci / pipeline (push) Failing after 2m29s
ci / images (api) (push) Skipped
ci / images (ml) (push) Skipped
ci / images (pipeline) (push) Skipped
ci / images (web) (push) Skipped
ci / api (push) Failing after 10s
ci / terraform (push) Failing after 11s
ci / web (push) Failing after 35s
ci / pipeline (push) Failing after 2m29s
ci / images (api) (push) Skipped
ci / images (ml) (push) Skipped
ci / images (pipeline) (push) Skipped
ci / images (web) (push) Skipped
End-to-end variant interpretation platform for rare genetic disease research: SvelteKit UI, FastAPI + PostgreSQL API, Nextflow/Ensembl VEP pipeline, LightGBM pathogenicity scoring with MLflow, K8s/ArgoCD/GCP infrastructure. Public test data only; no clinical claims.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Triggered by an Argo Events sensor listening on the Pub/Sub topic "vcf-uploaded".
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: WorkflowTemplate
|
||||
metadata: { name: annotate-vcf, namespace: rarelens }
|
||||
spec:
|
||||
entrypoint: nextflow
|
||||
arguments:
|
||||
parameters:
|
||||
- { name: job_id }
|
||||
- { name: vcf_uri }
|
||||
templates:
|
||||
- name: nextflow
|
||||
inputs:
|
||||
parameters: [{ name: job_id }, { name: vcf_uri }]
|
||||
serviceAccountName: rarelens-pipeline
|
||||
container:
|
||||
image: europe-west2-docker.pkg.dev/PROJECT/rarelens/pipeline:latest
|
||||
command: [nextflow]
|
||||
args:
|
||||
- run
|
||||
- /pipeline/main.nf
|
||||
- -profile
|
||||
- gcp
|
||||
- --vcf
|
||||
- "{{inputs.parameters.vcf_uri}}"
|
||||
- --job_id
|
||||
- "{{inputs.parameters.job_id}}"
|
||||
- --db_url
|
||||
- "$(DATABASE_URL)"
|
||||
envFrom: [{ secretRef: { name: api-secrets } }]
|
||||
resources: { requests: { cpu: "2", memory: 4Gi } }
|
||||
- name: score
|
||||
# Optional GPU step for the deep-learning baseline; Autopilot schedules on an L4 node.
|
||||
nodeSelector: { cloud.google.com/gke-accelerator: nvidia-l4 }
|
||||
container:
|
||||
image: europe-west2-docker.pkg.dev/PROJECT/rarelens/ml:latest
|
||||
resources: { limits: { nvidia.com/gpu: 1 } }
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata: { name: rarelens, namespace: argocd }
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/lynchaos/rarelens
|
||||
targetRevision: main
|
||||
path: infra/k8s/overlays/gcp
|
||||
destination: { server: https://kubernetes.default.svc, namespace: rarelens }
|
||||
syncPolicy:
|
||||
automated: { prune: true, selfHeal: true }
|
||||
syncOptions: [CreateNamespace=true]
|
||||
@@ -0,0 +1,26 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata: { name: api }
|
||||
spec:
|
||||
replicas: 2
|
||||
selector: { matchLabels: { app: api } }
|
||||
template:
|
||||
metadata: { labels: { app: api } }
|
||||
spec:
|
||||
serviceAccountName: rarelens-api
|
||||
containers:
|
||||
- name: api
|
||||
image: rarelens/api
|
||||
ports: [{ containerPort: 8000 }]
|
||||
envFrom: [{ secretRef: { name: api-secrets } }]
|
||||
readinessProbe: { httpGet: { path: /health, port: 8000 }, periodSeconds: 5 }
|
||||
resources: { requests: { cpu: 250m, memory: 512Mi }, limits: { cpu: "1", memory: 1Gi } }
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata: { name: api }
|
||||
spec: { selector: { app: api }, ports: [{ port: 80, targetPort: 8000 }] }
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata: { name: rarelens-api }
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata: { name: rarelens }
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- { path: /api, pathType: Prefix, backend: { service: { name: api, port: { number: 80 } } } }
|
||||
- { path: /, pathType: Prefix, backend: { service: { name: web, port: { number: 80 } } } }
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: rarelens
|
||||
resources: [namespace.yaml, api.yaml, web.yaml, ingress.yaml]
|
||||
@@ -0,0 +1,3 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata: { name: rarelens }
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata: { name: web }
|
||||
spec:
|
||||
replicas: 2
|
||||
selector: { matchLabels: { app: web } }
|
||||
template:
|
||||
metadata: { labels: { app: web } }
|
||||
spec:
|
||||
containers:
|
||||
- name: web
|
||||
image: rarelens/web
|
||||
ports: [{ containerPort: 3000 }]
|
||||
env: [{ name: PUBLIC_API_URL, value: /api }]
|
||||
resources: { requests: { cpu: 100m, memory: 128Mi }, limits: { cpu: 500m, memory: 256Mi } }
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata: { name: web }
|
||||
spec: { selector: { app: web }, ports: [{ port: 80, targetPort: 3000 }] }
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources: [../../base]
|
||||
images:
|
||||
- { name: rarelens/api, newName: europe-west2-docker.pkg.dev/PROJECT/rarelens/api, newTag: latest }
|
||||
- { name: rarelens/web, newName: europe-west2-docker.pkg.dev/PROJECT/rarelens/web, newTag: latest }
|
||||
patches:
|
||||
- target: { kind: ServiceAccount, name: rarelens-api }
|
||||
patch: |
|
||||
- op: add
|
||||
path: /metadata/annotations
|
||||
value: { iam.gke.io/gcp-service-account: [email protected] }
|
||||
- target: { kind: Deployment, name: api }
|
||||
patch: |
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/-
|
||||
value:
|
||||
name: cloud-sql-proxy
|
||||
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.13.0
|
||||
args: ["--structured-logs", "--port=5432", "PROJECT:europe-west2:rarelens-pg"]
|
||||
securityContext: { runAsNonRoot: true }
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources: [../../base, postgres.yaml]
|
||||
images:
|
||||
- { name: rarelens/api, newName: rarelens-api, newTag: dev }
|
||||
- { name: rarelens/web, newName: rarelens-web, newTag: dev }
|
||||
secretGenerator:
|
||||
- name: api-secrets
|
||||
literals: [DATABASE_URL=postgresql+asyncpg://rarelens:rarelens@postgres:5432/rarelens]
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata: { name: postgres }
|
||||
spec:
|
||||
selector: { matchLabels: { app: postgres } }
|
||||
template:
|
||||
metadata: { labels: { app: postgres } }
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
- { name: POSTGRES_USER, value: rarelens }
|
||||
- { name: POSTGRES_PASSWORD, value: rarelens }
|
||||
- { name: POSTGRES_DB, value: rarelens }
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata: { name: postgres }
|
||||
spec: { selector: { app: postgres }, ports: [{ port: 5432 }] }
|
||||
@@ -0,0 +1,29 @@
|
||||
resource "google_sql_database_instance" "pg" {
|
||||
name = "rarelens-pg"
|
||||
database_version = "POSTGRES_16"
|
||||
region = var.region
|
||||
deletion_protection = false
|
||||
|
||||
settings {
|
||||
tier = "db-f1-micro" # lab budget; bump for real use
|
||||
availability_type = "ZONAL"
|
||||
backup_configuration { enabled = true }
|
||||
ip_configuration {
|
||||
ipv4_enabled = false
|
||||
private_network = google_compute_network.vpc.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_sql_database" "rarelens" {
|
||||
name = "rarelens"
|
||||
instance = google_sql_database_instance.pg.name
|
||||
}
|
||||
|
||||
resource "google_sql_user" "api" {
|
||||
name = "rarelens"
|
||||
instance = google_sql_database_instance.pg.name
|
||||
password = random_password.pg.result
|
||||
}
|
||||
|
||||
resource "random_password" "pg" { length = 32 }
|
||||
@@ -0,0 +1,9 @@
|
||||
resource "google_container_cluster" "rarelens" {
|
||||
name = "rarelens"
|
||||
location = var.region
|
||||
enable_autopilot = true
|
||||
deletion_protection = false
|
||||
|
||||
workload_identity_config { workload_pool = "${var.project}.svc.id.goog" }
|
||||
release_channel { channel = "REGULAR" }
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Workload Identity Federation: GitHub Actions pushes images without long-lived keys.
|
||||
resource "google_iam_workload_identity_pool" "github" {
|
||||
workload_identity_pool_id = "github"
|
||||
}
|
||||
|
||||
resource "google_iam_workload_identity_pool_provider" "github" {
|
||||
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
|
||||
workload_identity_pool_provider_id = "github"
|
||||
attribute_mapping = {
|
||||
"google.subject" = "assertion.sub"
|
||||
"attribute.repository" = "assertion.repository"
|
||||
}
|
||||
attribute_condition = "assertion.repository == \"${var.github_repo}\""
|
||||
oidc { issuer_uri = "https://token.actions.githubusercontent.com" }
|
||||
}
|
||||
|
||||
resource "google_service_account" "ci" { account_id = "rarelens-ci" }
|
||||
|
||||
resource "google_service_account_iam_member" "ci_wif" {
|
||||
service_account_id = google_service_account.ci.name
|
||||
role = "roles/iam.workloadIdentityUser"
|
||||
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${var.github_repo}"
|
||||
}
|
||||
|
||||
resource "google_artifact_registry_repository_iam_member" "ci_push" {
|
||||
repository = google_artifact_registry_repository.images.name
|
||||
location = var.region
|
||||
role = "roles/artifactregistry.writer"
|
||||
member = "serviceAccount:${google_service_account.ci.email}"
|
||||
}
|
||||
|
||||
# Runtime identities (bound to k8s ServiceAccounts via GKE Workload Identity)
|
||||
resource "google_service_account" "api" { account_id = "rarelens-api" }
|
||||
resource "google_service_account" "pipeline" { account_id = "rarelens-pipeline" }
|
||||
|
||||
resource "google_project_iam_member" "api_sql" {
|
||||
project = var.project
|
||||
role = "roles/cloudsql.client"
|
||||
member = "serviceAccount:${google_service_account.api.email}"
|
||||
}
|
||||
resource "google_project_iam_member" "api_pubsub" {
|
||||
project = var.project
|
||||
role = "roles/pubsub.publisher"
|
||||
member = "serviceAccount:${google_service_account.api.email}"
|
||||
}
|
||||
resource "google_storage_bucket_iam_member" "pipeline_data" {
|
||||
bucket = google_storage_bucket.data.name
|
||||
role = "roles/storage.objectAdmin"
|
||||
member = "serviceAccount:${google_service_account.pipeline.email}"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
resource "google_compute_network" "vpc" {
|
||||
name = "rarelens-vpc"
|
||||
auto_create_subnetworks = true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
output "cluster_name" { value = google_container_cluster.rarelens.name }
|
||||
output "sql_connection" { value = google_sql_database_instance.pg.connection_name }
|
||||
output "data_bucket" { value = google_storage_bucket.data.name }
|
||||
output "wif_provider" { value = google_iam_workload_identity_pool_provider.github.name }
|
||||
output "ci_sa" { value = google_service_account.ci.email }
|
||||
@@ -0,0 +1,20 @@
|
||||
resource "google_storage_bucket" "data" {
|
||||
name = "${var.project}-rarelens-data"
|
||||
location = var.region
|
||||
uniform_bucket_level_access = true
|
||||
lifecycle_rule {
|
||||
condition {
|
||||
age = 30
|
||||
matches_prefix = ["work/"]
|
||||
}
|
||||
action { type = "Delete" }
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_artifact_registry_repository" "images" {
|
||||
repository_id = "rarelens"
|
||||
location = var.region
|
||||
format = "DOCKER"
|
||||
}
|
||||
|
||||
resource "google_pubsub_topic" "vcf_uploaded" { name = "vcf-uploaded" }
|
||||
@@ -0,0 +1,9 @@
|
||||
variable "project" { type = string }
|
||||
variable "region" {
|
||||
type = string
|
||||
default = "europe-west2" # London: keeps public genomic test data and the Cambridge team in one jurisdiction
|
||||
}
|
||||
variable "github_repo" {
|
||||
type = string
|
||||
default = "lynchaos/rarelens"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
terraform {
|
||||
required_version = ">= 1.8"
|
||||
required_providers {
|
||||
google = { source = "hashicorp/google", version = "~> 6.0" }
|
||||
random = { source = "hashicorp/random", version = "~> 3.6" }
|
||||
}
|
||||
backend "gcs" { bucket = "REPLACE-tfstate", prefix = "rarelens" }
|
||||
}
|
||||
|
||||
provider "google" {
|
||||
project = var.project
|
||||
region = var.region
|
||||
}
|
||||
Reference in New Issue
Block a user