Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5aa77be
brig-index: Don't fail when a user doc fails to build (#4839)
akshaymankar Apr 21, 2026
967673c
Merge pull request #5196 from wireapp/master
fisx Apr 21, 2026
5a94d23
WPB-00000: stabilize migration-related tests (#5198)
blackheaven Apr 22, 2026
c05e49a
build Docker image for mlsstats (#5200)
stefanwire Apr 23, 2026
a273968
feat: mlsstats version bump (#5201)
siberijah Apr 24, 2026
9540157
fix part number in mlsstats' upload function (#5203)
stefanwire Apr 24, 2026
52328d9
fix: helm fixes (#5202)
siberijah Apr 24, 2026
a2aa8c4
WPB-24072: Move operation to ConversationSubsystem (#5126)
blackheaven Apr 28, 2026
7a60d8d
feat: add SA for mlsstats (needed for PodIdentity) (#5206)
siberijah Apr 28, 2026
0b26efe
WPB-21964: Add Wire Meetings delete (#5066)
blackheaven Apr 28, 2026
57d5b15
Postgres: Index the parent_conv column in the conversation table (#5205)
akshaymankar Apr 29, 2026
55844e9
PostgresMigrations: Ensure only a migration runs once at a time, eve…
akshaymankar Apr 30, 2026
1c3f20e
PostgresMigrations: Don't block on migration lock (#5211)
akshaymankar Apr 30, 2026
d408e68
WPB-21359 fix: Inconsistent notifications are sent to local and remot…
battermann May 5, 2026
b4c8558
WPB-25109: Move meetings feature check from galley service layer into…
blackheaven May 5, 2026
57d02a6
[WPB-25136] Do not count apps as paying users. (#5213)
fisx May 7, 2026
7dcede4
[WPB-20053] Consolidate brig/galley api access effects from spar into…
fisx May 8, 2026
9e9d469
feat/nginz: add zone-based topology constraint (#5208)
siberijah May 8, 2026
e8c6f9f
Add changelog for Release 2026-05-08
zebot May 8, 2026
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
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
# [2026-05-08] (Chart Release 5.31.0)

## API changes


* `GET /teams/:tid/size` response body lists `teamSizeRegulars`, `teamSizeApps`. (#5213)


## Features


* `DELETE /meetings/:domain/:meetingId` for deleting meetings. (#5066)


## Bug fixes and other updates


* Fix: Inconsistent removal messages across local and federated conversation members (#5210)

* Do not count apps as paying users. (#5213)

* brig-index: Continue indexing even when an invalid user is found in the DB (#4839)

* Postgres: Index the parent_conv column in the conversation table (#5205, #5209, #5205)


## Internal changes


* Consolidate brig/galley api access effects from spar into wire-subsystems.

NB: calls to internal galley end-points were *sometimes* propagating unexpected errors (eg. 400) to the client, sometimes they were turned into a fixed 5xx error. we now consistently do the latter, which is more accurate (we don't want this to ever happen). (#5189)

* Using a random-generated index name to stabilize `testSearchNoExtraResults` (`brig-integration`). (#5198)

* Move conversation-related operations into a unified Polysemy `ConversationSubsystem` effect across the wire-server codebase. (#5126)

* Move meetings feature check from galley service layer into the MeetingsSubsystem interpreter, ensuring the check is enforced consistently within the subsystem. (#5214)

* Add tools/mlsstats to the Docker images to be built in CI runs.
mlsstats Helm chart now supports pod identity: ServiceAccount with configurable annotations is created and referenced by the CronJob pod, enabling OIDC-based AWS credential injection instead of static keys. AWS_EC2_METADATA_DISABLED is set so the AWS SDK uses web identity tokens on non-EC2 nodes. Secret creation is skipped when no static credentials are provided. (#5200)

* Add zone-level topologySpreadConstraints to nginz deployment so pods are spread evenly across availability zones, ensuring traffic reaches all AZs when zone-aware routing is in use.
topologySpreadConstraints are now configurable via values.yaml (defaulting to zone + hostname spreading), allowing chart users to customise pod scheduling without patching the chart. (#5208)


# [2026-04-20] (Chart Release 5.30.0)

## Release notes
Expand Down
12 changes: 7 additions & 5 deletions charts/mlsstats/templates/cronjob.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ spec:
backoffLimit: 0
template:
spec:
serviceAccountName: {{ .Values.serviceAccount.name }}
restartPolicy: Never
containers:
- name: mlsstats
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ default "" .Values.imagePullPolicy | quote }}
args: [ "mlsstats"
, "--brig-cassandra-host", {{ .Values.config.cassandra.brig.host | quote }}
args: [ "--brig-cassandra-host", {{ .Values.config.cassandra.brig.host | quote }}
, "--brig-cassandra-port", {{ .Values.config.cassandra.brig.port | quote }}
, "--brig-cassandra-keyspace", {{ .Values.config.cassandra.brig.keyspace | quote }}
, "--galley-cassandra-host", {{ .Values.config.cassandra.galley.host | quote }}
Expand All @@ -42,19 +42,21 @@ spec:
, "--s3-bucket-dir", {{ .Values.config.s3.bucket.directory | quote }}
]
resources:
{{ toYaml .Values.resources | indent 16 }}
env:
{{- if hasKey .Values.secrets "awsKeyId" }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: mlsstats
name: {{ .Chart.Name }}
key: awsKeyId
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: mlsstats
name: {{ .Chart.Name }}
key: awsSecretKey
{{- end }}
- name: AWS_REGION
value: "{{ .Values.config.s3.region }}"
{{ toYaml .Values.resources | indent 16 }}
- name: AWS_EC2_METADATA_DISABLED
value: "true"
15 changes: 5 additions & 10 deletions charts/mlsstats/templates/secret.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
{{- if .Values.secrets.awsKeyId }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}
name: {{ .Chart.Name }}
labels:
app: mlsstats
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
type: Opaque
data:
{{/* for_helm_linting is necessary only since the 'with' block below does not throw an error upon an empty .Values.secrets */}}
for_helm_linting: {{ required "No .secrets found in configuration. Did you forget to helm <command> -f path/to/secrets.yaml ?" .Values.secrets | quote | b64enc | quote }}

{{- with .Values.secrets }}
{{- if .awsKeyId }}
awsKeyId: {{ .awsKeyId | b64enc | quote }}
awsSecretKey: {{ .awsSecretKey | b64enc | quote }}
{{- end }}
{{- end }}
awsKeyId: {{ .Values.secrets.awsKeyId | b64enc | quote }}
awsSecretKey: {{ .Values.secrets.awsSecretKey | b64enc | quote }}
{{- end }}
16 changes: 16 additions & 0 deletions charts/mlsstats/templates/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name }}
labels:
app: mlsstats
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
{{- end }}
7 changes: 6 additions & 1 deletion charts/mlsstats/values.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
image:
repository: quay.io/wire/mlsstats
tag: 0.1
tag: 5.30.5
resources:
requests:
memory: "256Mi"
Expand All @@ -9,6 +9,11 @@ resources:
memory: "256Mi"
cpu: "100m"
schedule: "23 3 * * *"
serviceAccount:
create: true
name: mlsstats
annotations: {}
automountServiceAccountToken: true
config:
cassandra:
brig:
Expand Down
9 changes: 3 additions & 6 deletions charts/nginz/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,10 @@ spec:
fluentbit.io/parser-nginz: nginz
spec:
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: nginz
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: nginz
image: "{{ .Values.images.nginz.repository }}:{{ .Values.images.nginz.tag }}"
Expand Down
13 changes: 13 additions & 0 deletions charts/nginz/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ config:
wsPort: 8081
useProxyProtocol: true
terminationGracePeriodSeconds: 90
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: nginz
- maxSkew: 1
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: nginz
nginx_conf:
dns_resolver: kube-dns
cluster_domain: cluster.local
Expand Down
18 changes: 18 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
url = "github:brendanhay/amazonka?rev=a7d699be1076e2aad05a1930ca3937ffea954ad8";
flake = false;
};

hasql-migration = {
url = "github:wireapp/hasql-migration?ref=allow-no-transaction";
flake = false;
};
};

outputs = inputs@{ nixpkgs, nixpkgs_24_11, nixpkgs-unstable, flake-utils, tom-bombadil, sbomnix, ... }:
Expand Down
6 changes: 6 additions & 0 deletions integration/test/API/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,12 @@ activateSend domain email locale = do
req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["activate", "send"]
submit "POST" $ req & addJSONObject (["email" .= email] <> maybeToList (((.=) "locale") <$> locale))

-- https://staging-nginz-https.zinfra.io/v16/api/swagger-ui/#/default/get-team-size
getTeamSize :: (HasCallStack, MakesValue user) => user -> String -> App Response
getTeamSize user tid = do
req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "size"]
submit "GET" req

acceptTeamInvitation :: (HasCallStack, MakesValue user) => user -> String -> Maybe String -> App Response
acceptTeamInvitation user code mPw = do
req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", "invitations", "accept"]
Expand Down
5 changes: 5 additions & 0 deletions integration/test/API/BrigInternal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ refreshIndex domain = do
res <- submit "POST" req
res.status `shouldMatchInt` 200

getTeamSize :: (HasCallStack, MakesValue caller) => caller -> String -> App Response
getTeamSize caller tid = do
req <- baseRequest caller Brig Unversioned $ joinHttpPath ["i", "teams", tid, "size"]
submit "GET" req

addFederationRemoteTeam :: (HasCallStack, MakesValue domain, MakesValue remoteDomain, MakesValue team) => domain -> remoteDomain -> team -> App ()
addFederationRemoteTeam domain remoteDomain team = do
void $ addFederationRemoteTeam' domain remoteDomain team >>= getBody 200
Expand Down
5 changes: 5 additions & 0 deletions integration/test/API/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,11 @@ putMeeting user domain meetingId updatedMeeting = do
req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId])
submit "PUT" $ req & addJSON updatedMeeting

deleteMeeting :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
deleteMeeting user domain meetingId = do
req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId])
submit "DELETE" req

getMeeting :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
getMeeting user domain meetingId = do
req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId])
Expand Down
5 changes: 3 additions & 2 deletions integration/test/MLS/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,8 @@ consumingMessages mlsProtocol mp = Codensity $ \k -> do

-- at this point we know that every new user has been added to the
-- conversation
for_ (zip clients wss) $ \((cid, t), ws) -> case t of
let cssNoToBeRemoved = filter (\((ci, _), _) -> ci `Set.notMember` conv.membersToBeRemoved) (zip clients wss)
for_ cssNoToBeRemoved $ \((cid, t), ws) -> case t of
MLSNotificationMessageTag ->
when (conv.epoch > 0) $
void $
Expand Down Expand Up @@ -717,7 +718,7 @@ consumeMessageNoExternal cs cid mp = consumeMessageWithPredicate isNewMLSMessage
where
-- the backend (correctly) reacts to a commit removing someone from a parent conversation with a
-- remove proposal, however, we don't want to consume this here
isNewMLSMessageNotifButNoProposal :: Value -> App Bool
isNewMLSMessageNotifButNoProposal :: (HasCallStack) => Value -> App Bool
isNewMLSMessageNotifButNoProposal n = do
isRelevantNotif <- isNewMLSMessageNotif n &&~ isNotifConvId mp.convId n
if isRelevantNotif
Expand Down
34 changes: 33 additions & 1 deletion integration/test/Test/Apps.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

module Test.Apps where

import API.Brig
import API.Brig as Brig
import qualified API.BrigInternal as BrigI
import API.Common
import API.Galley
Expand All @@ -28,6 +28,7 @@ import Data.Aeson.QQ.Simple
import MLS.Util
import Notifications
import SetupHelpers
import System.Random (randomRIO)
import Testlib.Prelude

testCreateGetApp :: (HasCallStack) => Domain -> App ()
Expand Down Expand Up @@ -561,3 +562,34 @@ testAppReceivesMemberJoinNotification = do
memberJoinApp <- awaitMatch isTeamMemberJoinNotif wsApp
memberJoinApp %. "payload.0.team" `shouldMatch` tid
memberJoinApp %. "payload.0.data.user" `shouldMatch` objId newMember

testTeamSizeWithApps :: (HasCallStack) => TaggedBool "test internal api" -> App ()
testTeamSizeWithApps (TaggedBool testInternalApi) = do
domain <- make OwnDomain
numRegulars <- liftIO $ randomRIO (1 :: Int, 3)
numApps <- liftIO $ randomRIO (1 :: Int, 3)

(owner, tid, extraMembers) <- createTeam domain (numRegulars + 1)

apps <- replicateM numApps $ bindResponse (createApp owner tid def) $ \resp -> do
resp.status `shouldMatchInt` 200
resp.json %. "user"

let checkSize :: (HasCallStack) => Int -> Int -> App ()
checkSize wantRegulars wantApps =
(if testInternalApi then BrigI.getTeamSize else Brig.getTeamSize) owner tid `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
resp.json %. "teamSize" `shouldMatchInt` (1 + wantRegulars + wantApps)
resp.json %. "teamSizeRegulars" `shouldMatchInt` (1 + wantRegulars)
resp.json %. "teamSizeApps" `shouldMatchInt` wantApps

BrigI.refreshIndex domain
eventually $ do
checkSize numRegulars numApps

deleteTeamMember tid owner (head apps) >>= assertSuccess
deleteTeamMember tid owner (head extraMembers) >>= assertSuccess

BrigI.refreshIndex domain
eventually $ do
checkSize (numRegulars - 1) (numApps - 1)
10 changes: 10 additions & 0 deletions integration/test/Test/MLS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1209,3 +1209,13 @@ testGroupIdParseError = do
resp.json %. "label" `shouldMatch` "mls-protocol-error"
msg <- resp.json %. "message" & asString
assertBool "unexpected error message" $ "Could not parse group ID:" `isPrefixOf` msg

testFederatedRemove :: (HasCallStack) => App ()
testFederatedRemove = do
[alice, amy, bob] <- createAndConnectUsers [OwnDomain, OwnDomain, OtherDomain]
[alice1, amy1, bob1] <- traverse (createMLSClient def) [alice, amy, bob]
traverse_ (uploadNewKeyPackage def) [amy1, bob1]
convId <- createNewGroup def alice1

void $ createAddCommit alice1 convId [amy, bob] >>= sendAndConsumeCommitBundle
void $ createRemoveCommit alice1 convId [amy1, bob1] >>= sendAndConsumeCommitBundle
Loading