diff --git a/.env.gcp.template b/.env.gcp.template index 6c5bced521..5269692a3e 100644 --- a/.env.gcp.template +++ b/.env.gcp.template @@ -77,6 +77,10 @@ CLICKHOUSE_CLUSTER_SIZE=1 # Dashboard API instance count (default: 0) DASHBOARD_API_COUNT= +# Additional non-reserved dashboard-api env vars passed directly to the Nomad job (default: {}) +# Reserved keys managed by the module cannot be overridden here. +# Example: '{"SUPABASE_AUTH_USER_SYNC_ENABLED":"true"}' +DASHBOARD_API_ENV_VARS= # Filestore cache for builds shared across cluster (default:false) FILESTORE_CACHE_ENABLED= diff --git a/iac/modules/job-dashboard-api/jobs/dashboard-api.hcl b/iac/modules/job-dashboard-api/jobs/dashboard-api.hcl index f37d681cc9..f05aadcbb5 100644 --- a/iac/modules/job-dashboard-api/jobs/dashboard-api.hcl +++ b/iac/modules/job-dashboard-api/jobs/dashboard-api.hcl @@ -71,17 +71,9 @@ job "dashboard-api" { } env { - GIN_MODE = "release" - ENVIRONMENT = "${environment}" - NODE_ID = "$${node.unique.id}" - PORT = "$${NOMAD_PORT_api}" - POSTGRES_CONNECTION_STRING = "${postgres_connection_string}" - AUTH_DB_CONNECTION_STRING = "${auth_db_connection_string}" - AUTH_DB_READ_REPLICA_CONNECTION_STRING = "${auth_db_read_replica_connection_string}" - CLICKHOUSE_CONNECTION_STRING = "${clickhouse_connection_string}" - SUPABASE_JWT_SECRETS = "${supabase_jwt_secrets}" - OTEL_COLLECTOR_GRPC_ENDPOINT = "${otel_collector_grpc_endpoint}" - LOGS_COLLECTOR_ADDRESS = "${logs_collector_address}" + %{ for key in sort(keys(env)) ~} + ${key} = "${env[key]}" + %{ endfor ~} } config { diff --git a/iac/modules/job-dashboard-api/main.tf b/iac/modules/job-dashboard-api/main.tf index d1c455e3d1..26dcc2a889 100644 --- a/iac/modules/job-dashboard-api/main.tf +++ b/iac/modules/job-dashboard-api/main.tf @@ -1,3 +1,31 @@ +locals { + base_env = { + GIN_MODE = "release" + ENVIRONMENT = var.environment + NODE_ID = "$${node.unique.id}" + PORT = "$${NOMAD_PORT_api}" + POSTGRES_CONNECTION_STRING = var.postgres_connection_string + AUTH_DB_CONNECTION_STRING = var.auth_db_connection_string + AUTH_DB_READ_REPLICA_CONNECTION_STRING = var.auth_db_read_replica_connection_string + CLICKHOUSE_CONNECTION_STRING = var.clickhouse_connection_string + SUPABASE_JWT_SECRETS = var.supabase_jwt_secrets + OTEL_COLLECTOR_GRPC_ENDPOINT = "localhost:${var.otel_collector_grpc_port}" + LOGS_COLLECTOR_ADDRESS = "http://localhost:${var.logs_proxy_port.port}" + } + + extra_env = { + for key, value in var.extra_env : key => value + if value != null && trimspace(value) != "" + } + + conflicting_extra_env_keys = sort(tolist(setintersection( + toset(keys(local.base_env)), + toset(keys(local.extra_env)), + ))) + + env = merge(local.base_env, local.extra_env) +} + resource "nomad_job" "dashboard_api" { jobspec = templatefile("${path.module}/jobs/dashboard-api.hcl", { update_stanza = var.update_stanza @@ -10,15 +38,15 @@ resource "nomad_job" "dashboard_api" { memory_mb = 512 cpu_count = 1 - postgres_connection_string = var.postgres_connection_string - auth_db_connection_string = var.auth_db_connection_string - auth_db_read_replica_connection_string = var.auth_db_read_replica_connection_string - clickhouse_connection_string = var.clickhouse_connection_string - supabase_jwt_secrets = var.supabase_jwt_secrets + env = local.env subdomain = "dashboard-api" - - otel_collector_grpc_endpoint = "localhost:${var.otel_collector_grpc_port}" - logs_collector_address = "http://localhost:${var.logs_proxy_port.port}" }) + + lifecycle { + precondition { + condition = length(local.conflicting_extra_env_keys) == 0 + error_message = "dashboard-api extra_env contains reserved keys: ${join(", ", local.conflicting_extra_env_keys)}" + } + } } diff --git a/iac/modules/job-dashboard-api/variables.tf b/iac/modules/job-dashboard-api/variables.tf index 8b77e1c9a6..ab02574467 100644 --- a/iac/modules/job-dashboard-api/variables.tf +++ b/iac/modules/job-dashboard-api/variables.tf @@ -44,6 +44,11 @@ variable "supabase_jwt_secrets" { sensitive = true } +variable "extra_env" { + type = map(string) + default = {} +} + variable "otel_collector_grpc_port" { type = number default = 4317 diff --git a/iac/provider-gcp/Makefile b/iac/provider-gcp/Makefile index f1a44e7975..a8f2db309c 100644 --- a/iac/provider-gcp/Makefile +++ b/iac/provider-gcp/Makefile @@ -75,6 +75,7 @@ tf_vars := \ $(call tfvar, LOKI_BOOT_DISK_TYPE) \ $(call tfvar, LOKI_USE_V13_SCHEMA_FROM) \ $(call tfvar, DASHBOARD_API_COUNT) \ + $(call tfvar, DASHBOARD_API_ENV_VARS) \ $(call tfvar, DEFAULT_PERSISTENT_VOLUME_TYPE) \ $(call tfvar, PERSISTENT_VOLUME_TYPES) \ $(call tfvar, DB_MAX_OPEN_CONNECTIONS) \ diff --git a/iac/provider-gcp/main.tf b/iac/provider-gcp/main.tf index 8fe538e1fa..04f502e8a7 100644 --- a/iac/provider-gcp/main.tf +++ b/iac/provider-gcp/main.tf @@ -264,7 +264,8 @@ module "nomad" { otel_collector_resources_cpu_count = var.otel_collector_resources_cpu_count # Dashboard API - dashboard_api_count = var.dashboard_api_count + dashboard_api_count = var.dashboard_api_count + dashboard_api_env_vars = var.dashboard_api_env_vars # Docker reverse proxy docker_reverse_proxy_port = var.docker_reverse_proxy_port diff --git a/iac/provider-gcp/nomad/main.tf b/iac/provider-gcp/nomad/main.tf index f7889e2e35..21e17909b2 100644 --- a/iac/provider-gcp/nomad/main.tf +++ b/iac/provider-gcp/nomad/main.tf @@ -135,6 +135,7 @@ module "dashboard_api" { auth_db_read_replica_connection_string = trimspace(data.google_secret_manager_secret_version.postgres_read_replica_connection_string.secret_data) clickhouse_connection_string = local.clickhouse_connection_string supabase_jwt_secrets = trimspace(data.google_secret_manager_secret_version.supabase_jwt_secrets.secret_data) + extra_env = var.dashboard_api_env_vars otel_collector_grpc_port = var.otel_collector_grpc_port logs_proxy_port = var.logs_proxy_port diff --git a/iac/provider-gcp/nomad/variables.tf b/iac/provider-gcp/nomad/variables.tf index 54ead44500..6d179f5c86 100644 --- a/iac/provider-gcp/nomad/variables.tf +++ b/iac/provider-gcp/nomad/variables.tf @@ -453,6 +453,11 @@ variable "dashboard_api_count" { default = 0 } +variable "dashboard_api_env_vars" { + type = map(string) + default = {} +} + variable "volume_token_issuer" { type = string } diff --git a/iac/provider-gcp/variables.tf b/iac/provider-gcp/variables.tf index b8aee2ffaf..f4d8cfbf48 100644 --- a/iac/provider-gcp/variables.tf +++ b/iac/provider-gcp/variables.tf @@ -228,6 +228,11 @@ variable "dashboard_api_count" { default = 0 } +variable "dashboard_api_env_vars" { + type = map(string) + default = {} +} + variable "docker_reverse_proxy_port" { type = object({ name = string diff --git a/packages/api/go.mod b/packages/api/go.mod index 9d197cf26d..8a9763fe98 100644 --- a/packages/api/go.mod +++ b/packages/api/go.mod @@ -35,7 +35,7 @@ require ( github.com/google/uuid v1.6.0 github.com/grafana/loki/v3 v3.6.4 github.com/hashicorp/nomad/api v0.0.0-20251216171439-1dee0671280e - github.com/jackc/pgx/v5 v5.7.5 + github.com/jackc/pgx/v5 v5.9.1 github.com/launchdarkly/go-sdk-common/v3 v3.3.0 github.com/launchdarkly/go-server-sdk/v7 v7.13.0 github.com/oapi-codegen/gin-middleware v1.0.2 @@ -381,7 +381,7 @@ require ( golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect golang.org/x/image v0.38.0 // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.35.0 // indirect diff --git a/packages/api/go.sum b/packages/api/go.sum index 1d207bd808..f1c7dc11cf 100644 --- a/packages/api/go.sum +++ b/packages/api/go.sum @@ -557,8 +557,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= @@ -1159,8 +1159,8 @@ golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/packages/auth/go.mod b/packages/auth/go.mod index 27782f8e62..a545b30053 100644 --- a/packages/auth/go.mod +++ b/packages/auth/go.mod @@ -61,7 +61,7 @@ require ( github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jellydator/ttlcache/v3 v3.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/packages/auth/go.sum b/packages/auth/go.sum index 4b463c263c..d0e8192d66 100644 --- a/packages/auth/go.sum +++ b/packages/auth/go.sum @@ -114,8 +114,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= diff --git a/packages/clickhouse/go.mod b/packages/clickhouse/go.mod index e7f4c03e67..2b151949d7 100644 --- a/packages/clickhouse/go.mod +++ b/packages/clickhouse/go.mod @@ -39,7 +39,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect @@ -93,7 +93,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect diff --git a/packages/clickhouse/go.sum b/packages/clickhouse/go.sum index af8e90ffb7..c290bd8497 100644 --- a/packages/clickhouse/go.sum +++ b/packages/clickhouse/go.sum @@ -128,8 +128,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -310,8 +310,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/packages/client-proxy/go.mod b/packages/client-proxy/go.mod index 04bad15cce..0b041d6463 100644 --- a/packages/client-proxy/go.mod +++ b/packages/client-proxy/go.mod @@ -63,7 +63,7 @@ require ( go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect diff --git a/packages/client-proxy/go.sum b/packages/client-proxy/go.sum index dd487c5288..a8aa57bff1 100644 --- a/packages/client-proxy/go.sum +++ b/packages/client-proxy/go.sum @@ -207,8 +207,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= diff --git a/packages/dashboard-api/Makefile b/packages/dashboard-api/Makefile index 5ce6efda2a..1dfef7c5fe 100644 --- a/packages/dashboard-api/Makefile +++ b/packages/dashboard-api/Makefile @@ -13,6 +13,10 @@ endif HOSTNAME := $(shell hostname 2> /dev/null || hostnamectl hostname 2> /dev/null) $(if $(HOSTNAME),,$(error Failed to determine hostname: both 'hostname' and 'hostnamectl' failed)) +define DASHBOARD_API_EXTRA_ENV +$$(printf '%s' "$${DASHBOARD_API_ENV_VARS:-}" | jq -r '(if .=="" then empty elif type=="string" then (fromjson? // empty) else . end) | to_entries? // [] | map("\(.key)=\(.value|tostring|@sh)") | join(" ")') +endef + .PHONY: generate generate: go generate ./... @@ -33,12 +37,14 @@ build-and-upload: .PHONY: run run: make build - ./bin/dashboard-api + @EXTRA_ENV=$(DASHBOARD_API_EXTRA_ENV); \ + eval "env $$EXTRA_ENV ./bin/dashboard-api" .PHONY: run-local run-local: make build - NODE_ID=$(HOSTNAME) ./bin/dashboard-api + @EXTRA_ENV=$(DASHBOARD_API_EXTRA_ENV); \ + eval "env NODE_ID=$(HOSTNAME) $$EXTRA_ENV ./bin/dashboard-api" .PHONY: test test: diff --git a/packages/dashboard-api/go.mod b/packages/dashboard-api/go.mod index b4989c137a..cb4fa8809d 100644 --- a/packages/dashboard-api/go.mod +++ b/packages/dashboard-api/go.mod @@ -20,9 +20,14 @@ require ( github.com/gin-contrib/cors v1.7.6 github.com/gin-gonic/gin v1.10.1 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.5 + github.com/jackc/pgx/v5 v5.9.1 github.com/oapi-codegen/gin-middleware v1.0.2 github.com/oapi-codegen/runtime v1.1.1 + github.com/riverqueue/river v0.32.0 + github.com/riverqueue/river/riverdriver/riverpgxv5 v0.32.0 + github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/otel v1.41.0 + go.opentelemetry.io/otel/metric v1.41.0 go.uber.org/zap v1.27.1 ) @@ -112,14 +117,20 @@ require ( github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/pressly/goose/v3 v3.26.0 // indirect github.com/redis/go-redis/v9 v9.17.3 // indirect + github.com/riverqueue/river/riverdriver v0.32.0 // indirect + github.com/riverqueue/river/rivershared v0.32.0 // indirect + github.com/riverqueue/river/rivertype v0.32.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/shirou/gopsutil/v4 v4.25.9 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/testcontainers/testcontainers-go v0.40.0 // indirect github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -130,22 +141,21 @@ require ( go.opentelemetry.io/contrib/bridges/otelzap v0.14.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.66.0 // indirect - go.opentelemetry.io/otel v1.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect go.opentelemetry.io/otel/log v0.15.0 // indirect - go.opentelemetry.io/otel/metric v1.41.0 // indirect go.opentelemetry.io/otel/sdk v1.41.0 // indirect go.opentelemetry.io/otel/sdk/log v0.15.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect go.opentelemetry.io/otel/trace v1.41.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.18.0 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect diff --git a/packages/dashboard-api/go.sum b/packages/dashboard-api/go.sum index 8bc4994cc9..7a0dcff727 100644 --- a/packages/dashboard-api/go.sum +++ b/packages/dashboard-api/go.sum @@ -139,8 +139,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= @@ -245,6 +245,18 @@ github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1D github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/riverqueue/river v0.32.0 h1:j15EoFZ4oQWXcCq8NyzWwoi3fdaO8mECTB100NSv9Qw= +github.com/riverqueue/river v0.32.0/go.mod h1:zABAdLze3HI7K02N+veikXyK5FjiLzjimnQpZ1Duyng= +github.com/riverqueue/river/riverdriver v0.32.0 h1:AG6a2hNVOIGLx/+3IRtbwofJRYEI7xqnVVxULe9s4Lg= +github.com/riverqueue/river/riverdriver v0.32.0/go.mod h1:FRDMuqnLOsakeJOHlozKK+VH7W7NLp+6EToxQ2JAjBE= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.32.0 h1:CqrRxxcdA/0sHkxLNldsQff9DIG5qxn2EJO09Pau3w0= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.32.0/go.mod h1:j45UPpbMpcI10m+huTeNUaOwzoLJcEg0K6ihWXWeOec= +github.com/riverqueue/river/rivershared v0.32.0 h1:7DwdrppMU9uoU2iU9aGQiv91nBezjlcI85NV4PmnLHw= +github.com/riverqueue/river/rivershared v0.32.0/go.mod h1:UE7GEj3zaTV3cKw7Q3angCozlNEGsL50xZBKJQ9m6zU= +github.com/riverqueue/river/rivertype v0.32.0 h1:RW7uodfl86gYkjwDponTAPNnUqM+X6BjlsNHxbt6Ztg= +github.com/riverqueue/river/rivertype v0.32.0/go.mod h1:D1Ad+EaZiaXbQbJcJcfeicXJMBKno0n6UcfKI5Q7DIQ= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= @@ -275,7 +287,18 @@ github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+ github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 h1:REJz+XwNpGC/dCgTfYvM4SKqobNqDBfvhq74s2oHTUM= github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0/go.mod h1:4K2OhtHEeT+JSIFX4V8DkGKsyLa96Y2vLdd3xsxD5HE= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= @@ -353,8 +376,8 @@ golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05 golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/packages/dashboard-api/internal/backgroundworker/auth_user_sync.go b/packages/dashboard-api/internal/backgroundworker/auth_user_sync.go new file mode 100644 index 0000000000..b962bec78b --- /dev/null +++ b/packages/dashboard-api/internal/backgroundworker/auth_user_sync.go @@ -0,0 +1,139 @@ +package backgroundworker + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/riverqueue/river" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.uber.org/zap" + + sqlcdb "github.com/e2b-dev/infra/packages/db/client" + "github.com/e2b-dev/infra/packages/db/queries" + "github.com/e2b-dev/infra/packages/shared/pkg/logger" + "github.com/e2b-dev/infra/packages/shared/pkg/telemetry" +) + +const AuthUserSyncKind = "auth_user_sync" + +type AuthUserSyncArgs struct { + UserID string `json:"user_id"` + Operation string `json:"operation"` + Email string `json:"email,omitempty"` +} + +func (AuthUserSyncArgs) Kind() string { return AuthUserSyncKind } + +type AuthUserSyncWorker struct { + river.WorkerDefaults[AuthUserSyncArgs] + + mainDB *sqlcdb.Client + l logger.Logger + jobsCounter metric.Int64Counter +} + +func NewAuthUserSyncWorker(mainDB *sqlcdb.Client, l logger.Logger) *AuthUserSyncWorker { + jobsCounter, err := otel.Meter("github.com/e2b-dev/infra/packages/dashboard-api/internal/backgroundworker") + "dashboard_api.auth_user_sync.jobs_total", + metric.WithDescription("Total auth user sync jobs by operation and result."), + metric.WithUnit("{job}"), + ) + if err != nil { + l.Warn(context.Background(), "failed to initialize auth user sync metric", zap.Error(err)) + } + + return &AuthUserSyncWorker{ + mainDB: mainDB, + l: l, + jobsCounter: jobsCounter, + } +} + +func (w *AuthUserSyncWorker) Work(ctx context.Context, job *river.Job[AuthUserSyncArgs]) error { + attrs := []attribute.KeyValue{ + attribute.String("job.kind", AuthUserSyncKind), + attribute.String("job.operation", job.Args.Operation), + attribute.Int64("job.id", job.ID), + telemetry.WithUserID(job.Args.UserID), + } + telemetry.ReportEvent(ctx, "auth_user_sync.job.started", attrs...) + + userID, err := uuid.Parse(job.Args.UserID) + if err != nil { + telemetry.ReportError(ctx, "auth user sync parse user_id", err, attrs...) + w.observeJob(ctx, job.Args.Operation, "error") + + return fmt.Errorf("parse user_id %q: %w", job.Args.UserID, err) + } + + w.l.Info(ctx, "processing auth user sync job", + zap.String("job.kind", AuthUserSyncKind), + zap.Int64("job.id", job.ID), + zap.String("job.operation", job.Args.Operation), + logger.WithUserID(job.Args.UserID), + zap.Int("job.attempt", job.Attempt), + ) + + switch job.Args.Operation { + case "delete": + if err := w.mainDB.DeletePublicUser(ctx, userID); err != nil { + telemetry.ReportError(ctx, "auth user sync delete public user", err, attrs...) + w.observeJob(ctx, job.Args.Operation, "error") + + return fmt.Errorf("delete public.users %s: %w", userID, err) + } + + case "upsert": + if job.Args.Email == "" { + err := fmt.Errorf("missing email in job args") + telemetry.ReportError(ctx, "auth user sync missing email", err, attrs...) + w.observeJob(ctx, job.Args.Operation, "error") + + return fmt.Errorf("upsert public.users %s: missing email in job args", userID) + } + + if err := w.mainDB.UpsertPublicUser(ctx, queries.UpsertPublicUserParams{ + ID: userID, + Email: job.Args.Email, + }); err != nil { + telemetry.ReportError(ctx, "auth user sync upsert public user", err, attrs...) + w.observeJob(ctx, job.Args.Operation, "error") + + return fmt.Errorf("upsert public.users %s: %w", userID, err) + } + + default: + err := fmt.Errorf("unknown operation %q", job.Args.Operation) + telemetry.ReportError(ctx, "auth user sync unknown operation", err, attrs...) + w.observeJob(ctx, job.Args.Operation, "error") + + return fmt.Errorf("unknown operation %q for user %s", job.Args.Operation, userID) + } + + w.l.Info(ctx, "completed auth user sync job", + zap.String("job.kind", AuthUserSyncKind), + zap.Int64("job.id", job.ID), + zap.String("job.operation", job.Args.Operation), + logger.WithUserID(job.Args.UserID), + ) + telemetry.ReportEvent(ctx, "auth_user_sync.job.completed", attrs...) + w.observeJob(ctx, job.Args.Operation, "success") + + return nil +} + +func (w *AuthUserSyncWorker) observeJob(ctx context.Context, operation, result string) { + if w.jobsCounter == nil { + return + } + + w.jobsCounter.Add(ctx, 1, metric.WithAttributes( + attribute.String("worker", "supabase_auth_user_sync"), + attribute.String("job.kind", AuthUserSyncKind), + attribute.String("job.operation", operation), + attribute.String("result", result), + )) +} diff --git a/packages/dashboard-api/internal/backgroundworker/auth_user_sync_test.go b/packages/dashboard-api/internal/backgroundworker/auth_user_sync_test.go new file mode 100644 index 0000000000..7d0e928b66 --- /dev/null +++ b/packages/dashboard-api/internal/backgroundworker/auth_user_sync_test.go @@ -0,0 +1,252 @@ +package backgroundworker + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/riverqueue/river" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/db/pkg/testutils" + "github.com/e2b-dev/infra/packages/shared/pkg/logger" +) + +const ( + testEventuallyTimeout = 10 * time.Second + testEventuallyTick = 50 * time.Millisecond + testStopTimeout = 5 * time.Second +) + +type riverProcess struct { + cancel context.CancelFunc + done chan struct{} + stopOnce sync.Once +} + +func TestAuthUserSync_EndToEnd(t *testing.T) { + t.Parallel() + + db := testutils.SetupDatabase(t) + + authMigrationsDir := "packages/db/pkg/auth/migrations" + + db.ApplyMigrationsUpTo(t, 20260401000001, authMigrationsDir) + + authPool := db.AuthDb.WritePool() + require.NoError(t, RunRiverMigrations(t.Context(), authPool)) + + db.ApplyMigrations(t, authMigrationsDir) + + runUpsertProjection(t, db) + runDeleteProjection(t, db) + runBurstBacklog(t, db) +} + +func runUpsertProjection(t *testing.T, db *testutils.Database) { + t.Helper() + + ctx := t.Context() + userID := uuid.New() + email := fmt.Sprintf("river-sync-%s@example.com", userID.String()[:8]) + + proc := startRiverWorker(t, db) + t.Cleanup(func() { proc.Stop(t) }) + + insertAuthUser(t, ctx, db, userID, email) + + waitForPublicUser(t, ctx, db, userID, email) + + updatedEmail := fmt.Sprintf("river-sync-%s-updated@example.com", userID.String()[:8]) + updateAuthUserEmail(t, ctx, db, userID, updatedEmail) + + waitForPublicUser(t, ctx, db, userID, updatedEmail) + + proc.Stop(t) +} + +func runDeleteProjection(t *testing.T, db *testutils.Database) { + t.Helper() + + ctx := t.Context() + userID := uuid.New() + email := fmt.Sprintf("river-del-%s@example.com", userID.String()[:8]) + + proc := startRiverWorker(t, db) + t.Cleanup(func() { proc.Stop(t) }) + + insertAuthUser(t, ctx, db, userID, email) + waitForPublicUser(t, ctx, db, userID, email) + + deleteAuthUser(t, ctx, db, userID) + waitForPublicUserGone(t, ctx, db, userID) + + proc.Stop(t) +} + +func runBurstBacklog(t *testing.T, db *testutils.Database) { + t.Helper() + + ctx := t.Context() + const userCount = 40 + + type testUser struct { + id uuid.UUID + email string + shouldDel bool + } + + users := make([]testUser, 0, userCount) + for i := range userCount { + u := testUser{ + id: uuid.New(), + email: fmt.Sprintf("river-burst-%02d@example.com", i), + shouldDel: i%3 == 0, + } + users = append(users, u) + insertAuthUser(t, ctx, db, u.id, u.email) + } + + proc := startRiverWorker(t, db) + t.Cleanup(func() { proc.Stop(t) }) + + for _, u := range users { + waitForPublicUser(t, ctx, db, u.id, u.email) + } + + for _, u := range users { + if u.shouldDel { + deleteAuthUser(t, ctx, db, u.id) + } + } + + for _, u := range users { + if u.shouldDel { + waitForPublicUserGone(t, ctx, db, u.id) + } else { + waitForPublicUser(t, ctx, db, u.id, u.email) + } + } + + proc.Stop(t) +} + +func startRiverWorker(t *testing.T, db *testutils.Database) *riverProcess { + t.Helper() + + authPool := db.AuthDb.WritePool() + l := logger.NewNopLogger() + + workers := river.NewWorkers() + river.AddWorker(workers, NewAuthUserSyncWorker(db.SqlcClient, l)) + + client, err := NewRiverClient(authPool, workers) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + require.NoError(t, client.Start(ctx)) + + done := make(chan struct{}) + + go func() { + <-ctx.Done() + stopCtx, stopCancel := context.WithTimeout(context.WithoutCancel(ctx), testStopTimeout) + defer stopCancel() + + _ = client.Stop(stopCtx) + close(done) + }() + + return &riverProcess{cancel: cancel, done: done} +} + +func (p *riverProcess) Stop(t *testing.T) { + t.Helper() + + p.stopOnce.Do(func() { + p.cancel() + + select { + case <-p.done: + case <-time.After(testStopTimeout): + t.Fatal("river client did not stop in time") + } + }) +} + +func insertAuthUser(t *testing.T, ctx context.Context, db *testutils.Database, userID uuid.UUID, email string) { + t.Helper() + + err := db.AuthDb.TestsRawSQL(ctx, + "INSERT INTO auth.users (id, email) VALUES ($1, $2)", userID, email) + require.NoError(t, err) +} + +func updateAuthUserEmail(t *testing.T, ctx context.Context, db *testutils.Database, userID uuid.UUID, email string) { + t.Helper() + + err := db.AuthDb.TestsRawSQL(ctx, + "UPDATE auth.users SET email = $1 WHERE id = $2", email, userID) + require.NoError(t, err) +} + +func deleteAuthUser(t *testing.T, ctx context.Context, db *testutils.Database, userID uuid.UUID) { + t.Helper() + + err := db.AuthDb.TestsRawSQL(ctx, + "DELETE FROM auth.users WHERE id = $1", userID) + require.NoError(t, err) +} + +func waitForPublicUser(t *testing.T, ctx context.Context, db *testutils.Database, userID uuid.UUID, expectedEmail string) { + t.Helper() + + require.EventuallyWithT(t, func(c *assert.CollectT) { + var email string + + err := db.AuthDb.TestsRawSQLQuery(ctx, + "SELECT email FROM public.users WHERE id = $1", + func(rows pgx.Rows) error { + if !rows.Next() { + return fmt.Errorf("user %s not found in public.users", userID) + } + + return rows.Scan(&email) + }, userID) + + if !assert.NoError(c, err) { + return + } + + assert.Equal(c, expectedEmail, email) + }, testEventuallyTimeout, testEventuallyTick) +} + +func waitForPublicUserGone(t *testing.T, ctx context.Context, db *testutils.Database, userID uuid.UUID) { + t.Helper() + + require.EventuallyWithT(t, func(c *assert.CollectT) { + var count int + + err := db.AuthDb.TestsRawSQLQuery(ctx, + "SELECT count(*) FROM public.users WHERE id = $1", + func(rows pgx.Rows) error { + if !rows.Next() { + return nil + } + + return rows.Scan(&count) + }, userID) + + if !assert.NoError(c, err) { + return + } + + assert.Equal(c, 0, count) + }, testEventuallyTimeout, testEventuallyTick) +} diff --git a/packages/dashboard-api/internal/backgroundworker/river.go b/packages/dashboard-api/internal/backgroundworker/river.go new file mode 100644 index 0000000000..a248833067 --- /dev/null +++ b/packages/dashboard-api/internal/backgroundworker/river.go @@ -0,0 +1,38 @@ +package backgroundworker + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/riverqueue/river" + "github.com/riverqueue/river/riverdriver/riverpgxv5" + "github.com/riverqueue/river/rivermigrate" +) + +const AuthCustomSchema = "auth_custom" + +func RunRiverMigrations(ctx context.Context, pool *pgxpool.Pool) error { + driver := riverpgxv5.New(pool) + + migrator, err := rivermigrate.New(driver, &rivermigrate.Config{ + Schema: AuthCustomSchema, + }) + if err != nil { + return err + } + + _, err = migrator.Migrate(ctx, rivermigrate.DirectionUp, nil) + + return err +} + +func NewRiverClient(pool *pgxpool.Pool, workers *river.Workers) (*river.Client[pgx.Tx], error) { + return river.NewClient(riverpgxv5.New(pool), &river.Config{ + Schema: AuthCustomSchema, + Queues: map[string]river.QueueConfig{ + "auth_sync": {MaxWorkers: 10}, + }, + Workers: workers, + }) +} diff --git a/packages/dashboard-api/internal/cfg/model.go b/packages/dashboard-api/internal/cfg/model.go index f0d9ad10a1..bbd83194f6 100644 --- a/packages/dashboard-api/internal/cfg/model.go +++ b/packages/dashboard-api/internal/cfg/model.go @@ -1,8 +1,6 @@ package cfg -import ( - "github.com/caarlos0/env/v11" -) +import "github.com/caarlos0/env/v11" type Config struct { Port int `env:"PORT" envDefault:"3010"` @@ -12,6 +10,8 @@ type Config struct { AuthDBConnectionString string `env:"AUTH_DB_CONNECTION_STRING"` AuthDBReadReplicaConnectionString string `env:"AUTH_DB_READ_REPLICA_CONNECTION_STRING"` + + SupabaseAuthUserSyncEnabled bool `env:"SUPABASE_AUTH_USER_SYNC_ENABLED" envDefault:"false"` } func Parse() (Config, error) { diff --git a/packages/dashboard-api/main.go b/packages/dashboard-api/main.go index 4b40634473..e6df2d9eb4 100644 --- a/packages/dashboard-api/main.go +++ b/packages/dashboard-api/main.go @@ -20,7 +20,9 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/google/uuid" + "github.com/jackc/pgx/v5" middleware "github.com/oapi-codegen/gin-middleware" + "github.com/riverqueue/river" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -28,6 +30,7 @@ import ( "github.com/e2b-dev/infra/packages/auth/pkg/types" clickhouse "github.com/e2b-dev/infra/packages/clickhouse/pkg" "github.com/e2b-dev/infra/packages/dashboard-api/internal/api" + "github.com/e2b-dev/infra/packages/dashboard-api/internal/backgroundworker" "github.com/e2b-dev/infra/packages/dashboard-api/internal/cfg" "github.com/e2b-dev/infra/packages/dashboard-api/internal/handlers" sqlcdb "github.com/e2b-dev/infra/packages/db/client" @@ -229,6 +232,31 @@ func run() int { wg := sync.WaitGroup{} + var riverClient *river.Client[pgx.Tx] + + if config.SupabaseAuthUserSyncEnabled { + workerLogger := l.With(zap.String("worker", "supabase_auth_user_sync")) + + authPool := authDB.WritePool() + if err := backgroundworker.RunRiverMigrations(ctx, authPool); err != nil { + l.Fatal(ctx, "failed to run River migrations on auth DB", zap.Error(err)) + } + + workers := river.NewWorkers() + river.AddWorker(workers, backgroundworker.NewAuthUserSyncWorker(db, workerLogger)) + + riverClient, err = backgroundworker.NewRiverClient(authPool, workers) + if err != nil { + l.Fatal(ctx, "failed to create River client", zap.Error(err)) + } + + if err := riverClient.Start(signalCtx); err != nil { + l.Fatal(ctx, "failed to start River client", zap.Error(err)) + } + + l.Info(ctx, "background worker started (River auth_custom)", zap.String("queue", "auth_sync")) + } + wg.Go(func() { <-signalCtx.Done() l.Info(ctx, "Shutting down dashboard-api service...") @@ -236,6 +264,14 @@ func run() int { shutdownCtx, shutdownCancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) defer shutdownCancel() + if riverClient != nil { + if err := riverClient.Stop(shutdownCtx); err != nil { + l.Error(ctx, "River client shutdown error", zap.Error(err)) + + errorCode.Add(1) + } + } + if err := s.Shutdown(shutdownCtx); err != nil { l.Error(ctx, "HTTP server shutdown error", zap.Error(err)) diff --git a/packages/db/go.mod b/packages/db/go.mod index 22cbf8515a..f69bcb99b5 100644 --- a/packages/db/go.mod +++ b/packages/db/go.mod @@ -14,7 +14,7 @@ require ( github.com/exaring/otelpgx v0.9.3 github.com/google/uuid v1.6.0 github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 - github.com/jackc/pgx/v5 v5.7.5 + github.com/jackc/pgx/v5 v5.9.1 github.com/lib/pq v1.11.2 github.com/pressly/goose/v3 v3.26.0 github.com/stretchr/testify v1.11.1 @@ -140,7 +140,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect diff --git a/packages/db/go.sum b/packages/db/go.sum index b21cf6fa1b..b3908e0377 100644 --- a/packages/db/go.sum +++ b/packages/db/go.sum @@ -175,8 +175,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -424,8 +424,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/packages/db/pkg/auth/client.go b/packages/db/pkg/auth/client.go index 02a1dcf2a0..fe9f33d666 100644 --- a/packages/db/pkg/auth/client.go +++ b/packages/db/pkg/auth/client.go @@ -60,6 +60,10 @@ func (db *Client) Close() error { return nil } +func (db *Client) WritePool() *pgxpool.Pool { + return db.writeConn +} + // WithTx runs the given function in a transaction. func (db *Client) WithTx(ctx context.Context) (*authqueries.Queries, pgx.Tx, error) { tx, err := db.writeConn.BeginTx(ctx, pgx.TxOptions{}) diff --git a/packages/db/pkg/auth/migrations/20260401000001_river_auth_custom_schema.sql b/packages/db/pkg/auth/migrations/20260401000001_river_auth_custom_schema.sql new file mode 100644 index 0000000000..73afedfbe3 --- /dev/null +++ b/packages/db/pkg/auth/migrations/20260401000001_river_auth_custom_schema.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- +goose StatementBegin + +CREATE SCHEMA IF NOT EXISTS auth_custom; + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +/* We don't want to drop the schema, as it is used by other services. */ + +-- +goose StatementEnd diff --git a/packages/db/pkg/auth/migrations/20260401000003_river_auth_user_sync_triggers.sql b/packages/db/pkg/auth/migrations/20260401000003_river_auth_user_sync_triggers.sql new file mode 100644 index 0000000000..ba3ee8737c --- /dev/null +++ b/packages/db/pkg/auth/migrations/20260401000003_river_auth_user_sync_triggers.sql @@ -0,0 +1,99 @@ +-- +goose Up +-- +goose StatementBegin + +CREATE OR REPLACE FUNCTION auth_custom.enqueue_user_sync_on_insert() +RETURNS TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER SET search_path = '' +AS $$ +BEGIN + INSERT INTO auth_custom.river_job (args, kind, max_attempts, queue, state) + VALUES ( + jsonb_build_object('user_id', NEW.id, 'operation', 'upsert', 'email', NEW.email), + 'auth_user_sync', + 20, + 'auth_sync', + 'available' + ); + + PERFORM pg_notify('auth_custom.river_insert', '{"queue":"auth_sync"}'); + + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION auth_custom.enqueue_user_sync_on_update() +RETURNS TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER SET search_path = '' +AS $$ +BEGIN + IF OLD.email IS DISTINCT FROM NEW.email THEN + INSERT INTO auth_custom.river_job (args, kind, max_attempts, queue, state) + VALUES ( + jsonb_build_object('user_id', NEW.id, 'operation', 'upsert', 'email', NEW.email), + 'auth_user_sync', + 20, + 'auth_sync', + 'available' + ); + + PERFORM pg_notify('auth_custom.river_insert', '{"queue":"auth_sync"}'); + END IF; + + RETURN NEW; +END; +$$; + +CREATE OR REPLACE FUNCTION auth_custom.enqueue_user_sync_on_delete() +RETURNS TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER SET search_path = '' +AS $$ +BEGIN + INSERT INTO auth_custom.river_job (args, kind, max_attempts, queue, state) + VALUES ( + jsonb_build_object('user_id', OLD.id, 'operation', 'delete'), + 'auth_user_sync', + 20, + 'auth_sync', + 'available' + ); + + PERFORM pg_notify('auth_custom.river_insert', '{"queue":"auth_sync"}'); + + RETURN OLD; +END; +$$; + +CREATE TRIGGER enqueue_user_sync_on_insert + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION auth_custom.enqueue_user_sync_on_insert(); + +CREATE TRIGGER enqueue_user_sync_on_update + AFTER UPDATE ON auth.users + FOR EACH ROW EXECUTE FUNCTION auth_custom.enqueue_user_sync_on_update(); + +CREATE TRIGGER enqueue_user_sync_on_delete + AFTER DELETE ON auth.users + FOR EACH ROW EXECUTE FUNCTION auth_custom.enqueue_user_sync_on_delete(); + +GRANT INSERT ON auth_custom.river_job TO trigger_user; +GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA auth_custom TO trigger_user; + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +DROP TRIGGER IF EXISTS enqueue_user_sync_on_insert ON auth.users; +DROP TRIGGER IF EXISTS enqueue_user_sync_on_update ON auth.users; +DROP TRIGGER IF EXISTS enqueue_user_sync_on_delete ON auth.users; + +DROP FUNCTION IF EXISTS auth_custom.enqueue_user_sync_on_insert(); +DROP FUNCTION IF EXISTS auth_custom.enqueue_user_sync_on_update(); +DROP FUNCTION IF EXISTS auth_custom.enqueue_user_sync_on_delete(); + +REVOKE ALL ON SCHEMA auth_custom FROM trigger_user; + +-- +goose StatementEnd diff --git a/packages/db/pkg/auth/queries/ack.sql.go b/packages/db/pkg/auth/queries/ack.sql.go new file mode 100644 index 0000000000..2102f263db --- /dev/null +++ b/packages/db/pkg/auth/queries/ack.sql.go @@ -0,0 +1,20 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: ack.sql + +package authqueries + +import ( + "context" +) + +const ackUserSyncQueueItem = `-- name: AckUserSyncQueueItem :exec +DELETE FROM public.user_sync_queue +WHERE id = $1::bigint +` + +func (q *Queries) AckUserSyncQueueItem(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, ackUserSyncQueueItem, id) + return err +} diff --git a/packages/db/pkg/auth/queries/ack_batch.sql.go b/packages/db/pkg/auth/queries/ack_batch.sql.go new file mode 100644 index 0000000000..1b0d49a0af --- /dev/null +++ b/packages/db/pkg/auth/queries/ack_batch.sql.go @@ -0,0 +1,20 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: ack_batch.sql + +package authqueries + +import ( + "context" +) + +const ackUserSyncQueueItems = `-- name: AckUserSyncQueueItems :exec +DELETE FROM public.user_sync_queue +WHERE id = ANY($1::bigint[]) +` + +func (q *Queries) AckUserSyncQueueItems(ctx context.Context, ids []int64) error { + _, err := q.db.Exec(ctx, ackUserSyncQueueItems, ids) + return err +} diff --git a/packages/db/pkg/auth/queries/claim_batch.sql.go b/packages/db/pkg/auth/queries/claim_batch.sql.go new file mode 100644 index 0000000000..6c1555549f --- /dev/null +++ b/packages/db/pkg/auth/queries/claim_batch.sql.go @@ -0,0 +1,73 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: claim_batch.sql + +package authqueries + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +const claimUserSyncQueueBatch = `-- name: ClaimUserSyncQueueBatch :many +UPDATE public.user_sync_queue +SET + locked_at = now(), + lock_owner = $1::text, + attempt_count = attempt_count + 1 +WHERE id IN ( + SELECT id + FROM public.user_sync_queue + WHERE dead_lettered_at IS NULL + AND next_attempt_at <= now() + AND (locked_at IS NULL OR locked_at < now() - $2::interval) + ORDER BY id + FOR UPDATE SKIP LOCKED + LIMIT $3::int +) +RETURNING id, user_id, operation, created_at, attempt_count +` + +type ClaimUserSyncQueueBatchParams struct { + LockOwner string + LockTimeout pgtype.Interval + BatchSize int32 +} + +type ClaimUserSyncQueueBatchRow struct { + ID int64 + UserID uuid.UUID + Operation string + CreatedAt time.Time + AttemptCount int32 +} + +func (q *Queries) ClaimUserSyncQueueBatch(ctx context.Context, arg ClaimUserSyncQueueBatchParams) ([]ClaimUserSyncQueueBatchRow, error) { + rows, err := q.db.Query(ctx, claimUserSyncQueueBatch, arg.LockOwner, arg.LockTimeout, arg.BatchSize) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ClaimUserSyncQueueBatchRow + for rows.Next() { + var i ClaimUserSyncQueueBatchRow + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Operation, + &i.CreatedAt, + &i.AttemptCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/packages/db/pkg/auth/queries/dead_letter.sql.go b/packages/db/pkg/auth/queries/dead_letter.sql.go new file mode 100644 index 0000000000..de2bcb3e80 --- /dev/null +++ b/packages/db/pkg/auth/queries/dead_letter.sql.go @@ -0,0 +1,30 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: dead_letter.sql + +package authqueries + +import ( + "context" +) + +const deadLetterUserSyncQueueItem = `-- name: DeadLetterUserSyncQueueItem :exec +UPDATE public.user_sync_queue +SET + locked_at = NULL, + lock_owner = NULL, + dead_lettered_at = now(), + last_error = $1::text +WHERE id = $2::bigint +` + +type DeadLetterUserSyncQueueItemParams struct { + LastError string + ID int64 +} + +func (q *Queries) DeadLetterUserSyncQueueItem(ctx context.Context, arg DeadLetterUserSyncQueueItemParams) error { + _, err := q.db.Exec(ctx, deadLetterUserSyncQueueItem, arg.LastError, arg.ID) + return err +} diff --git a/packages/db/pkg/auth/queries/get_auth_user.sql.go b/packages/db/pkg/auth/queries/get_auth_user.sql.go new file mode 100644 index 0000000000..4c34da8bdf --- /dev/null +++ b/packages/db/pkg/auth/queries/get_auth_user.sql.go @@ -0,0 +1,25 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: get_auth_user.sql + +package authqueries + +import ( + "context" + + "github.com/google/uuid" +) + +const getAuthUserByID = `-- name: GetAuthUserByID :one +SELECT id, email +FROM auth.users +WHERE id = $1::uuid +` + +func (q *Queries) GetAuthUserByID(ctx context.Context, userID uuid.UUID) (AuthUser, error) { + row := q.db.QueryRow(ctx, getAuthUserByID, userID) + var i AuthUser + err := row.Scan(&i.ID, &i.Email) + return i, err +} diff --git a/packages/db/pkg/auth/queries/retry.sql.go b/packages/db/pkg/auth/queries/retry.sql.go new file mode 100644 index 0000000000..297fbd7c76 --- /dev/null +++ b/packages/db/pkg/auth/queries/retry.sql.go @@ -0,0 +1,33 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: retry.sql + +package authqueries + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const retryUserSyncQueueItem = `-- name: RetryUserSyncQueueItem :exec +UPDATE public.user_sync_queue +SET + locked_at = NULL, + lock_owner = NULL, + next_attempt_at = now() + $1::interval, + last_error = $2::text +WHERE id = $3::bigint +` + +type RetryUserSyncQueueItemParams struct { + Backoff pgtype.Interval + LastError string + ID int64 +} + +func (q *Queries) RetryUserSyncQueueItem(ctx context.Context, arg RetryUserSyncQueueItemParams) error { + _, err := q.db.Exec(ctx, retryUserSyncQueueItem, arg.Backoff, arg.LastError, arg.ID) + return err +} diff --git a/packages/db/pkg/testutils/db.go b/packages/db/pkg/testutils/db.go index edde569887..a9fd66e4c4 100644 --- a/packages/db/pkg/testutils/db.go +++ b/packages/db/pkg/testutils/db.go @@ -39,8 +39,15 @@ type Database struct { SqlcClient *db.Client AuthDb *authdb.Client TestQueries *queries.Queries + connStr string } +// gooseMu serializes goose operations across parallel tests. +// goose.OpenDBWithDriver calls goose.SetDialect which writes to package-level +// globals (dialect, store) without synchronization. Concurrent test goroutines +// race on these globals, triggering the race detector on ARM64. +var gooseMu sync.Mutex + // SetupDatabase creates a fresh PostgreSQL container with migrations applied func SetupDatabase(t *testing.T) *Database { t.Helper() @@ -104,17 +111,27 @@ func SetupDatabase(t *testing.T) *Database { SqlcClient: sqlcClient, AuthDb: authDb, TestQueries: testQueries, + connStr: connStr, } } -// gooseMu serializes goose operations across parallel tests. -// goose.OpenDBWithDriver calls goose.SetDialect which writes to package-level -// globals (dialect, store) without synchronization. Concurrent test goroutines -// race on these globals, triggering the race detector on ARM64. -var gooseMu sync.Mutex +func (db *Database) ApplyMigrations(t *testing.T, migrationDirs ...string) { + t.Helper() -// runDatabaseMigrations executes all required database migrations -func runDatabaseMigrations(t *testing.T, connStr string) { + db.applyGooseMigrations(t, 0, migrationDirs...) +} + +func (db *Database) ApplyMigrationsUpTo(t *testing.T, version int64, migrationDirs ...string) { + t.Helper() + + db.applyGooseMigrations(t, version, migrationDirs...) +} + +func (db *Database) ConnStr() string { + return db.connStr +} + +func (db *Database) applyGooseMigrations(t *testing.T, upToVersion int64, migrationDirs ...string) { t.Helper() cmd := exec.CommandContext(t.Context(), "git", "rev-parse", "--show-toplevel") @@ -125,20 +142,39 @@ func runDatabaseMigrations(t *testing.T, connStr string) { gooseMu.Lock() defer gooseMu.Unlock() - db, err := goose.OpenDBWithDriver("pgx", connStr) + sqlDB, err := goose.OpenDBWithDriver("pgx", db.connStr) require.NoError(t, err) t.Cleanup(func() { - err := db.Close() + err := sqlDB.Close() assert.NoError(t, err) }) - // run the db migration - err = goose.RunWithOptionsContext( - t.Context(), - "up", - db, - filepath.Join(repoRoot, "packages", "db", "migrations"), - nil, - ) - require.NoError(t, err) + for _, migrationsDir := range migrationDirs { + if upToVersion > 0 { + err = goose.UpToContext( + t.Context(), + sqlDB, + filepath.Join(repoRoot, migrationsDir), + upToVersion, + ) + } else { + err = goose.RunWithOptionsContext( + t.Context(), + "up", + sqlDB, + filepath.Join(repoRoot, migrationsDir), + nil, + ) + } + + require.NoError(t, err) + } +} + +// runDatabaseMigrations executes all required database migrations +func runDatabaseMigrations(t *testing.T, connStr string) { + t.Helper() + + db := &Database{connStr: connStr} + db.ApplyMigrations(t, filepath.Join("packages", "db", "migrations")) } diff --git a/packages/db/queries/delete_public_user.sql.go b/packages/db/queries/delete_public_user.sql.go new file mode 100644 index 0000000000..585d8c977c --- /dev/null +++ b/packages/db/queries/delete_public_user.sql.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: delete_public_user.sql + +package queries + +import ( + "context" + + "github.com/google/uuid" +) + +const deletePublicUser = `-- name: DeletePublicUser :exec +DELETE FROM public.users +WHERE id = $1::uuid +` + +func (q *Queries) DeletePublicUser(ctx context.Context, id uuid.UUID) error { + _, err := q.db.Exec(ctx, deletePublicUser, id) + return err +} diff --git a/packages/db/queries/upsert_public_user.sql.go b/packages/db/queries/upsert_public_user.sql.go new file mode 100644 index 0000000000..dc0fd6eba3 --- /dev/null +++ b/packages/db/queries/upsert_public_user.sql.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: upsert_public_user.sql + +package queries + +import ( + "context" + + "github.com/google/uuid" +) + +const upsertPublicUser = `-- name: UpsertPublicUser :exec +INSERT INTO public.users (id, email) +VALUES ($1::uuid, $2::text) +ON CONFLICT (id) +DO UPDATE SET + email = EXCLUDED.email, + updated_at = now() +` + +type UpsertPublicUserParams struct { + ID uuid.UUID + Email string +} + +func (q *Queries) UpsertPublicUser(ctx context.Context, arg UpsertPublicUserParams) error { + _, err := q.db.Exec(ctx, upsertPublicUser, arg.ID, arg.Email) + return err +} diff --git a/packages/db/queries/users/delete_public_user.sql b/packages/db/queries/users/delete_public_user.sql new file mode 100644 index 0000000000..492f1051cd --- /dev/null +++ b/packages/db/queries/users/delete_public_user.sql @@ -0,0 +1,3 @@ +-- name: DeletePublicUser :exec +DELETE FROM public.users +WHERE id = sqlc.arg(id)::uuid; diff --git a/packages/db/queries/users/upsert_public_user.sql b/packages/db/queries/users/upsert_public_user.sql new file mode 100644 index 0000000000..ebabd969e6 --- /dev/null +++ b/packages/db/queries/users/upsert_public_user.sql @@ -0,0 +1,7 @@ +-- name: UpsertPublicUser :exec +INSERT INTO public.users (id, email) +VALUES (sqlc.arg(id)::uuid, sqlc.arg(email)::text) +ON CONFLICT (id) +DO UPDATE SET + email = EXCLUDED.email, + updated_at = now(); diff --git a/packages/db/sqlc.yaml b/packages/db/sqlc.yaml index 206c468907..4959602d19 100644 --- a/packages/db/sqlc.yaml +++ b/packages/db/sqlc.yaml @@ -63,6 +63,7 @@ sql: schema: - "migrations" - "schema" + - "pkg/auth/migrations" gen: go: emit_pointers_for_null_types: true diff --git a/packages/docker-reverse-proxy/go.mod b/packages/docker-reverse-proxy/go.mod index 2f663be82b..c39a14622f 100644 --- a/packages/docker-reverse-proxy/go.mod +++ b/packages/docker-reverse-proxy/go.mod @@ -40,7 +40,7 @@ require ( github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.9.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/lib/pq v1.11.2 // indirect @@ -79,7 +79,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect diff --git a/packages/docker-reverse-proxy/go.sum b/packages/docker-reverse-proxy/go.sum index 9d0c348df3..fa1e0b24f5 100644 --- a/packages/docker-reverse-proxy/go.sum +++ b/packages/docker-reverse-proxy/go.sum @@ -69,8 +69,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= @@ -189,8 +189,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= diff --git a/packages/envd/go.mod b/packages/envd/go.mod index 6175013054..4d86a8140b 100644 --- a/packages/envd/go.mod +++ b/packages/envd/go.mod @@ -73,7 +73,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/text v0.35.0 // indirect diff --git a/packages/envd/go.sum b/packages/envd/go.sum index 0834f4de21..fa0212436b 100644 --- a/packages/envd/go.sum +++ b/packages/envd/go.sum @@ -213,8 +213,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/packages/local-dev/go.mod b/packages/local-dev/go.mod index bd14d7b6ba..8429d7768d 100644 --- a/packages/local-dev/go.mod +++ b/packages/local-dev/go.mod @@ -10,7 +10,7 @@ require ( github.com/e2b-dev/infra/packages/db v0.0.0 github.com/e2b-dev/infra/packages/shared v0.0.0 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.5 + github.com/jackc/pgx/v5 v5.9.1 github.com/pressly/goose/v3 v3.26.0 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 diff --git a/packages/local-dev/go.sum b/packages/local-dev/go.sum index 5878ebb18b..def89b00d0 100644 --- a/packages/local-dev/go.sum +++ b/packages/local-dev/go.sum @@ -69,8 +69,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= diff --git a/packages/orchestrator/go.mod b/packages/orchestrator/go.mod index cd896e8d7a..0d5dde3fe9 100644 --- a/packages/orchestrator/go.mod +++ b/packages/orchestrator/go.mod @@ -309,7 +309,7 @@ require ( golang.org/x/arch v0.18.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/term v0.40.0 // indirect diff --git a/packages/orchestrator/go.sum b/packages/orchestrator/go.sum index f75731182b..17645baedb 100644 --- a/packages/orchestrator/go.sum +++ b/packages/orchestrator/go.sum @@ -1428,8 +1428,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/packages/shared/go.mod b/packages/shared/go.mod index 093aa0ae83..e1d9394cc7 100644 --- a/packages/shared/go.mod +++ b/packages/shared/go.mod @@ -54,7 +54,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.41.0 go.opentelemetry.io/otel/trace v1.41.0 go.uber.org/zap v1.27.1 - golang.org/x/mod v0.33.0 + golang.org/x/mod v0.34.0 golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.20.0 google.golang.org/api v0.257.0 diff --git a/packages/shared/go.sum b/packages/shared/go.sum index a58712d7d6..69f3c15d7f 100644 --- a/packages/shared/go.sum +++ b/packages/shared/go.sum @@ -1023,8 +1023,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/tests/integration/go.mod b/tests/integration/go.mod index a0e9a6bb59..7a4019b2de 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -25,7 +25,7 @@ require ( github.com/e2b-dev/infra/packages/envd v0.0.0-00010101000000-000000000000 github.com/e2b-dev/infra/packages/shared v0.0.0 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.5 + github.com/jackc/pgx/v5 v5.9.1 github.com/oapi-codegen/runtime v1.1.1 github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.20.0 @@ -176,7 +176,7 @@ require ( go.uber.org/zap v1.27.1 // indirect golang.org/x/arch v0.18.0 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 83d28a5089..4ef6ca9f30 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -182,8 +182,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= @@ -447,8 +447,8 @@ golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkN golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=