Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/push-gateway-helm-chart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
pull_request:
paths:
- "deploy/charts/buzz-push-gateway/**"
- "crates/buzz-push-gateway/src/config.rs"
- ".github/workflows/push-gateway-helm-chart.yml"
permissions: {}
env:
Expand All @@ -27,6 +28,7 @@ jobs:
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
fetch-depth: 0
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4
with: { version: v3.16.4 }
- run: deploy/charts/buzz-push-gateway/tests/render.sh
Expand Down
24 changes: 24 additions & 0 deletions crates/buzz-push-gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ mod tests {
("BUZZ_PUSH_DOGFOOD_APNS_ENVIRONMENT", "staging"),
("BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS", "0"),
("BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS", "31536001"),
("BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS", "2.592e+06"),
("BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS", "2592000.5"),
("BUZZ_PUSH_MAX_INSTALLATION_LIFETIME_SECONDS", "0"),
] {
let mut env = base();
Expand All @@ -457,6 +459,28 @@ mod tests {
}
}

// The chart test supplies actual Helm-rendered environment values. Keep this
// separate from ordinary unit tests, which do not require Helm or fixtures.
#[test]
#[ignore = "run deploy/charts/buzz-push-gateway/tests/grant-lifetime.sh"]
fn helm_rendered_grant_lifetimes() {
let path = std::env::var("BUZZ_TEST_HELM_ENV_FILE").unwrap();
let cases: Vec<(String, i64, HashMap<String, String>)> =
serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
assert_eq!(cases.len(), 9);
for (label, expected, rendered) in cases {
let mut env = base();
env.extend(rendered);
let config = Config::from_map(&env).unwrap_or_else(|error| panic!("{label}: {error}"));
assert_eq!(config.max_grant_lifetime_seconds, expected, "{label}");
assert_eq!(
env["BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS"],
expected.to_string(),
"{label} must render decimal integer text"
);
}
}

#[test]
fn cross_keyring_id_or_material_reuse_fails_startup() {
for token_keys in [
Expand Down
2 changes: 1 addition & 1 deletion deploy/charts/buzz-push-gateway/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ apiVersion: v2
# branches (see docs/push-gateway-deployment.md, "Gateway chart release").
name: buzz-push-gateway
description: Public capability-gated APNs last-hop gateway for Buzz
version: 0.3.1
version: 0.3.2
Comment thread
brow marked this conversation as resolved.
appVersion: "0.1.0"
Comment thread
brow marked this conversation as resolved.
type: application
3 changes: 2 additions & 1 deletion deploy/charts/buzz-push-gateway/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ spec:
- { name: BUZZ_PUSH_BIND_ADDR, value: "0.0.0.0:8080" }
- { name: BUZZ_PUSH_HEALTH_ADDR, value: "0.0.0.0:8081" }
- { name: BUZZ_PUSH_GATEWAY_ORIGIN, value: {{ required "gatewayOrigin is required" .Values.gatewayOrigin | quote }} }
- { name: BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS, value: {{ .Values.maxGrantLifetimeSeconds | quote }} }
{{/* Schema bounds this integer; normalize YAML numbers before quote can emit scientific notation. */}}
- { name: BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS, value: {{ .Values.maxGrantLifetimeSeconds | int64 | quote }} }
- { name: BUZZ_PUSH_APP_ATTEST_ROOT_CERT_PATH, value: /run/buzz/app-attest/root.pem }
- { name: BUZZ_PUSH_DOGFOOD_APP_ATTEST_APP_ID, value: {{ .Values.profiles.dogfood.appAttestAppId | quote }} }
- { name: BUZZ_PUSH_DOGFOOD_APNS_TOPIC, value: {{ .Values.profiles.dogfood.apnsTopic | quote }} }
Expand Down
46 changes: 46 additions & 0 deletions deploy/charts/buzz-push-gateway/tests/grant-lifetime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
fixture=$(mktemp)
trap 'rm -f "$fixture"' EXIT

# Use real Helm renders, including YAML values (not just --set, which can
# preserve a different numeric type). No deployment or real secrets are needed.
env -u GEM_HOME -u GEM_PATH -u RUBYLIB -u RUBYOPT ruby -rjson -ryaml -ropen3 \
- "$fixture" <<'RUBY'
chart = 'deploy/charts/buzz-push-gateway'
command = ['helm', 'template', 'push', chart, '--set', 'gatewayOrigin=https://push.example']
cases = []
render = lambda do |label, expected, args, values|
output, error, status = Open3.capture3(*command, *args, stdin_data: values)
raise "#{label}: Helm failed: #{error}" unless status.success?
deployment = YAML.load_stream(output).compact.find { |x| x['kind'] == 'Deployment' }
entries = deployment.fetch('spec').fetch('template').fetch('spec').fetch('containers')[0].fetch('env')
# Secret references are intentionally excluded; Config's fixture supplies
# synthetic keyrings and database configuration in memory.
literal_env = entries.select { |entry| entry.key?('value') }.to_h do |entry|
value = entry.fetch('value')
raise "#{label}: #{entry['name']} is not a string" unless value.is_a?(String)
[entry.fetch('name'), value]
end
raise "#{label}: lifetime missing" unless literal_env.key?('BUZZ_PUSH_MAX_GRANT_LIFETIME_SECONDS')
cases << [label, expected, literal_env]
end
render.call('default', 2592000, [], '')
[1, 2592000, 2592001, 31536000].each do |seconds|
render.call("values #{seconds}", seconds, ['-f', '-'], "maxGrantLifetimeSeconds: #{seconds}\n")
render.call("set #{seconds}", seconds, ['--set', "maxGrantLifetimeSeconds=#{seconds}"], '')
end

# Integer conversion must never hide invalid inputs. Helm's schema remains
# authoritative, including rejecting strings that merely look numeric.
['0', '-1', '31536001', '2592000.5', '"2592000"', 'true'].each do |value|
_, error, status = Open3.capture3(*command, '-f', '-', stdin_data: "maxGrantLifetimeSeconds: #{value}\n")
unless !status.success? && error.include?('maxGrantLifetimeSeconds')
raise "expected schema rejection for #{value}: #{error}"
end
end
File.write(ARGV.fetch(0), JSON.generate(cases))
RUBY

BUZZ_TEST_HELM_ENV_FILE="$fixture" cargo test -p buzz-push-gateway --lib \
config::tests::helm_rendered_grant_lifetimes -- --ignored --exact
1 change: 1 addition & 0 deletions deploy/charts/buzz-push-gateway/tests/render.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
bash deploy/charts/buzz-push-gateway/tests/grant-lifetime.sh
out=$(mktemp); production_out=$(mktemp); route_out=$(mktemp); datadog_out=$(mktemp)
trap 'rm -f "$out" "$production_out" "$route_out" "$datadog_out" "${monitoring_out:-}"' EXIT
gateway_origin_arg=(--set 'gatewayOrigin=https://push.example')
Expand Down
10 changes: 8 additions & 2 deletions docs/push-gateway-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,14 @@ Kubernetes does not restart pods when referenced Secret bytes change. AEAD or AP
The gateway chart has a collision-free release lane separate from the main
`buzz` chart. To publish chart version `X.Y.Z`, update `version` in
`deploy/charts/buzz-push-gateway/Chart.yaml` and keep `appVersion` equal to the
gateway binary's workspace package version. Validate the chart, then open a
same-repository PR whose branch is exactly `push-chart-release/X.Y.Z`:
gateway binary's workspace package version.

The render tests require Helm, Ruby, and the repository's Rust toolchain. They
pass rendered environment values into the gateway's configuration parser with
synthetic credentials; no running gateway or database is needed.

Validate the chart, then open a same-repository PR whose branch is exactly
`push-chart-release/X.Y.Z`:

```bash
deploy/charts/buzz-push-gateway/tests/render.sh
Expand Down
Loading