From 6879f481525948a4da8cfa5ee31ab1643cef63ae Mon Sep 17 00:00:00 2001 From: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz> Date: Tue, 28 Jul 2026 15:29:19 -0700 Subject: [PATCH] fix(storage): close S3 deployment gaps Honor shared URL addressing in the Git probe, expose the signing region through Helm, and make storage validation guidance match the actual startup probe behavior. Co-authored-by: Kalvin Chau Signed-off-by: Kalvin Chau --- Cargo.lock | 1 + crates/buzz-test-client/Cargo.toml | 1 + crates/buzz-test-client/tests/e2e_git.rs | 71 ++++++++++++++++--- deploy/charts/buzz/README.md | 44 +++++++----- deploy/charts/buzz/examples/argocd-app.yaml | 1 + .../buzz/examples/flux-helmrelease.yaml | 1 + deploy/charts/buzz/templates/_validate.tpl | 14 ++-- deploy/charts/buzz/templates/deployment.yaml | 1 + deploy/charts/buzz/tests/render_test.yaml | 13 ++++ deploy/charts/buzz/values.schema.json | 5 ++ deploy/charts/buzz/values.yaml | 3 + 11 files changed, 119 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b60dc457..c3ea86d6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1239,6 +1239,7 @@ dependencies = [ "anyhow", "base64", "buzz-core", + "buzz-media", "buzz-sdk", "buzz-ws-client", "chrono", diff --git a/crates/buzz-test-client/Cargo.toml b/crates/buzz-test-client/Cargo.toml index 40a08f3d1..e495c1630 100644 --- a/crates/buzz-test-client/Cargo.toml +++ b/crates/buzz-test-client/Cargo.toml @@ -36,6 +36,7 @@ sha2 = { workspace = true } sqlx = { workspace = true } chrono = { workspace = true } s3 = { version = "0.37", package = "rust-s3", default-features = false, features = ["tokio-rustls-tls", "fail-on-err", "tags"] } +buzz-media = { workspace = true } buzz-sdk = { workspace = true } [[bin]] diff --git a/crates/buzz-test-client/tests/e2e_git.rs b/crates/buzz-test-client/tests/e2e_git.rs index 63281fd18..989d3b733 100644 --- a/crates/buzz-test-client/tests/e2e_git.rs +++ b/crates/buzz-test-client/tests/e2e_git.rs @@ -21,6 +21,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::time::Duration; +use buzz_media::S3AddressingStyle; use nostr::{EventBuilder, Keys, Kind, Tag}; use s3::creds::Credentials; use s3::{Bucket, Region}; @@ -115,6 +116,27 @@ struct PointerSnapshot { } impl GitS3Probe { + fn bucket( + endpoint: String, + access_key: &str, + secret_key: &str, + bucket_name: &str, + region_name: String, + addressing_style: S3AddressingStyle, + ) -> Box { + let region = Region::Custom { + region: region_name, + endpoint, + }; + let creds = Credentials::new(Some(access_key), Some(secret_key), None, None, None) + .expect("S3 credentials"); + let bucket = Bucket::new(bucket_name, region, creds).expect("S3 bucket"); + match addressing_style { + S3AddressingStyle::Path => bucket.with_path_style(), + S3AddressingStyle::Virtual => bucket, + } + } + fn from_env() -> Self { let endpoint = std::env::var("BUZZ_GIT_S3_ENDPOINT") .or_else(|_| std::env::var("BUZZ_S3_ENDPOINT")) @@ -125,19 +147,25 @@ impl GitS3Probe { let secret_key = std::env::var("BUZZ_GIT_S3_SECRET_KEY") .or_else(|_| std::env::var("BUZZ_S3_SECRET_KEY")) .unwrap_or_else(|_| "buzz_dev_secret".to_string()); - let bucket = std::env::var("BUZZ_GIT_S3_BUCKET") + let bucket_name = std::env::var("BUZZ_GIT_S3_BUCKET") .or_else(|_| std::env::var("BUZZ_S3_BUCKET")) .unwrap_or_else(|_| "buzz-media".to_string()); + let region_name = std::env::var("BUZZ_GIT_S3_REGION") + .or_else(|_| std::env::var("BUZZ_S3_REGION")) + .unwrap_or_else(|_| "us-east-1".to_string()); + let addressing_style = std::env::var("BUZZ_S3_ADDRESSING_STYLE") + .unwrap_or_else(|_| "path".to_string()) + .parse::() + .expect("BUZZ_S3_ADDRESSING_STYLE must be 'path' or 'virtual'"); - let region = Region::Custom { - region: "us-east-1".into(), + let bucket = Self::bucket( endpoint, - }; - let creds = Credentials::new(Some(&access_key), Some(&secret_key), None, None, None) - .expect("S3 credentials"); - let bucket = Bucket::new(&bucket, region, creds) - .expect("S3 bucket") - .with_path_style(); + &access_key, + &secret_key, + &bucket_name, + region_name, + addressing_style, + ); Self { bucket } } @@ -192,6 +220,31 @@ impl GitS3Probe { } } +#[test] +fn git_s3_probe_builds_both_addressing_styles() { + let path = GitS3Probe::bucket( + "https://storage.example".to_string(), + "access", + "secret", + "buzz-media", + "us-east-1".to_string(), + S3AddressingStyle::Path, + ); + assert!(path.is_path_style()); + assert_eq!(path.url(), "https://storage.example/buzz-media"); + + let virtual_hosted = GitS3Probe::bucket( + "https://storage.example".to_string(), + "access", + "secret", + "buzz-media", + "auto".to_string(), + S3AddressingStyle::Virtual, + ); + assert!(virtual_hosted.is_subdomain_style()); + assert_eq!(virtual_hosted.url(), "https://buzz-media.storage.example"); +} + #[tokio::test] #[ignore = "requires live relay + MinIO + git"] async fn git_clone_push_fetch_force_roundtrip() { diff --git a/deploy/charts/buzz/README.md b/deploy/charts/buzz/README.md index c35f66d4d..2e4d37820 100644 --- a/deploy/charts/buzz/README.md +++ b/deploy/charts/buzz/README.md @@ -61,28 +61,36 @@ Buzz uses one URL style for both media and Git/CAS object-store requests: | `path` (default) | `https://endpoint/bucket/key` | Bundled MinIO and endpoints whose DNS does not resolve bucket subdomains | | `virtual` | `https://bucket.endpoint/key` | AWS-style providers and new Railway Storage Buckets | -The chart renders this value as `BUZZ_S3_ADDRESSING_STYLE`. Only `path` and -`virtual` are accepted; invalid values fail chart rendering and relay startup. -The bundled MinIO quickstart deliberately keeps `path` because its Service DNS -resolves one endpoint hostname, not arbitrary `.` names. +The chart renders `s3.region` and `s3.addressingStyle` as `BUZZ_S3_REGION` +and `BUZZ_S3_ADDRESSING_STYLE`. Only `path` and `virtual` addressing styles are +accepted; invalid values fail chart rendering and relay startup. The bundled +MinIO quickstart deliberately keeps `path` because its Service DNS resolves one +endpoint hostname, not arbitrary `.` names. -For a Railway Storage Bucket named `Object Storage`, map its variables onto the -Buzz service and set the style explicitly: +For a Railway Storage Bucket, map its variables to chart values in the service +or generated Helm configuration: -```text -BUZZ_S3_ENDPOINT=${{Object Storage.ENDPOINT}} -BUZZ_S3_BUCKET=${{Object Storage.BUCKET}} -BUZZ_S3_ACCESS_KEY=${{Object Storage.ACCESS_KEY_ID}} -BUZZ_S3_SECRET_KEY=${{Object Storage.SECRET_ACCESS_KEY}} -BUZZ_S3_REGION=${{Object Storage.REGION}} -BUZZ_S3_ADDRESSING_STYLE=virtual +```yaml +s3: + endpoint: "${{Object Storage.ENDPOINT}}" + bucket: "${{Object Storage.BUCKET}}" + region: "${{Object Storage.REGION}}" + addressingStyle: virtual ``` -Railway's Credentials tab is authoritative for older buckets, which may still -require `path`. The setting changes request routing and SigV4 signing, so do not -put the bucket into `BUZZ_S3_ENDPOINT`; pass Railway's base `ENDPOINT` and -`BUCKET` separately. The relay validates storage before becoming ready, and the -Git conformance probe remains startup-fatal when enabled. +Store `BUZZ_S3_ACCESS_KEY=${{Object Storage.ACCESS_KEY_ID}}` and +`BUZZ_S3_SECRET_KEY=${{Object Storage.SECRET_ACCESS_KEY}}` in the Secret named by +`secrets.existingSecret`. Railway's Credentials tab is authoritative for older +buckets, which may still require `path`. The setting changes request routing and +SigV4 signing, so do not put the bucket into `s3.endpoint`; pass Railway's base +`ENDPOINT` and `BUCKET` separately. + +Object storage is contacted during relay startup only when +`BUZZ_GIT_CONFORMANCE_PROBE` is enabled (the relay default). A probe failure is +startup-fatal, so Kubernetes readiness never opens. If an operator explicitly +disables that probe through `relay.extraEnv`, `/_readiness` does not test object +storage; configuration is still parsed strictly, but reachability and addressing +errors surface on the first storage operation. ## Relay Pod extensions diff --git a/deploy/charts/buzz/examples/argocd-app.yaml b/deploy/charts/buzz/examples/argocd-app.yaml index 6f77d9c35..8f6cb7622 100644 --- a/deploy/charts/buzz/examples/argocd-app.yaml +++ b/deploy/charts/buzz/examples/argocd-app.yaml @@ -41,6 +41,7 @@ spec: s3: endpoint: "https://s3.us-east-1.amazonaws.com" bucket: "buzz-media" + region: "us-east-1" addressingStyle: virtual # accessKey / secretKey live in buzz-secrets diff --git a/deploy/charts/buzz/examples/flux-helmrelease.yaml b/deploy/charts/buzz/examples/flux-helmrelease.yaml index df978c958..09a6bfeb6 100644 --- a/deploy/charts/buzz/examples/flux-helmrelease.yaml +++ b/deploy/charts/buzz/examples/flux-helmrelease.yaml @@ -41,6 +41,7 @@ spec: s3: endpoint: "https://s3.us-east-1.amazonaws.com" bucket: "buzz-media" + region: "us-east-1" addressingStyle: virtual persistence: diff --git a/deploy/charts/buzz/templates/_validate.tpl b/deploy/charts/buzz/templates/_validate.tpl index 732367539..aa7f7ac13 100644 --- a/deploy/charts/buzz/templates/_validate.tpl +++ b/deploy/charts/buzz/templates/_validate.tpl @@ -75,16 +75,12 @@ surface at template time regardless of which manifest helm renders first. {{- fail "Postgres source missing: enable postgresql.enabled=true, set externalPostgresql.url, or provide secrets.existingSecret with key DATABASE_URL." -}} {{- end -}} -{{/* S3 addressing style is duplicated here in addition to values.schema.json - because Helm unittest does not consistently apply schema validation. */}} -{{- if not (has .Values.s3.addressingStyle (list "path" "virtual")) -}} - {{- fail "s3.addressingStyle must be 'path' or 'virtual'. Use path for bundled MinIO and virtual for providers such as new Railway Storage Buckets." -}} -{{- end -}} - -{{/* S3 / object-storage source must exist somewhere (relay hard-fails its - startup conformance probe without a reachable bucket). */}} +{{/* S3 / object-storage source must exist somewhere. With the default + BUZZ_GIT_CONFORMANCE_PROBE behavior, an unreachable bucket is detected + before the relay opens its listener; operators can explicitly disable that + startup gate. */}} {{- if not (or .Values.minio.enabled .Values.s3.endpoint .Values.secrets.existingSecret) -}} - {{- fail "S3/object-storage source missing: enable minio.enabled=true (quickstart in-cluster), set s3.endpoint + s3.bucket + credentials, or provide secrets.existingSecret with keys BUZZ_S3_ACCESS_KEY + BUZZ_S3_SECRET_KEY. The relay runs a startup S3 conformance probe and exits if storage is unreachable." -}} + {{- fail "S3/object-storage source missing: enable minio.enabled=true (quickstart in-cluster), set s3.endpoint + s3.bucket + credentials, or provide secrets.existingSecret with keys BUZZ_S3_ACCESS_KEY + BUZZ_S3_SECRET_KEY. By default the relay runs a startup S3 conformance probe and exits if storage is unreachable; disabling BUZZ_GIT_CONFORMANCE_PROBE also removes that startup storage check." -}} {{- end -}} {{- end -}} diff --git a/deploy/charts/buzz/templates/deployment.yaml b/deploy/charts/buzz/templates/deployment.yaml index d3a11557d..41a292633 100644 --- a/deploy/charts/buzz/templates/deployment.yaml +++ b/deploy/charts/buzz/templates/deployment.yaml @@ -170,6 +170,7 @@ spec: - { name: BUZZ_S3_ENDPOINT, value: {{ $s3Endpoint | quote }} } {{- end }} - { name: BUZZ_S3_BUCKET, value: {{ .Values.s3.bucket | quote }} } + - { name: BUZZ_S3_REGION, value: {{ .Values.s3.region | quote }} } - { name: BUZZ_S3_ADDRESSING_STYLE, value: {{ .Values.s3.addressingStyle | quote }} } # ── Secrets (from chart-managed or existing) ───────────── diff --git a/deploy/charts/buzz/tests/render_test.yaml b/deploy/charts/buzz/tests/render_test.yaml index 104aa7cd1..0d23661b0 100644 --- a/deploy/charts/buzz/tests/render_test.yaml +++ b/deploy/charts/buzz/tests/render_test.yaml @@ -30,6 +30,12 @@ tests: path: kind value: Service template: templates/service.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: BUZZ_S3_REGION + value: "us-east-1" + template: templates/deployment.yaml - contains: path: spec.template.spec.containers[0].env content: @@ -61,10 +67,17 @@ tests: externalRedis.url: redis://h:6379 s3.endpoint: https://storage.railway.app s3.bucket: buzz-media-example + s3.region: auto s3.addressingStyle: virtual s3.accessKey: a s3.secretKey: s asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: BUZZ_S3_REGION + value: "auto" + template: templates/deployment.yaml - contains: path: spec.template.spec.containers[0].env content: diff --git a/deploy/charts/buzz/values.schema.json b/deploy/charts/buzz/values.schema.json index 4c0153dbb..8a7de8173 100644 --- a/deploy/charts/buzz/values.schema.json +++ b/deploy/charts/buzz/values.schema.json @@ -198,6 +198,11 @@ "properties": { "endpoint": { "type": "string", "pattern": "^(https?://.+)?$" }, "bucket": { "type": "string", "minLength": 1 }, + "region": { + "type": "string", + "minLength": 1, + "description": "S3 region used for SigV4 signing. Defaults to us-east-1 for bundled MinIO compatibility." + }, "addressingStyle": { "type": "string", "enum": ["path", "virtual"], diff --git a/deploy/charts/buzz/values.yaml b/deploy/charts/buzz/values.yaml index 381746914..71249003d 100644 --- a/deploy/charts/buzz/values.yaml +++ b/deploy/charts/buzz/values.yaml @@ -338,6 +338,9 @@ externalRedis: s3: endpoint: "" bucket: "buzz-media" + # SigV4 signing region. Keep us-east-1 for bundled MinIO; set this to the + # provider's credential value for managed S3 (for example Railway's REGION). + region: "us-east-1" # path: https://endpoint/bucket/key (bundled MinIO-compatible default) # virtual: https://bucket.endpoint/key (standard S3; required by new Railway buckets) addressingStyle: path