feat(GODT-2759): CLI debug commands

Add debug commands to CLI to diagnose potential bride problems.
Currently we only have a command which validates whether the state of
all the mailboxes reported by IMAP matches what is currently available
on the proton servers.
This commit is contained in:
Leander Beernaert
2023-07-05 09:51:55 +02:00
parent f545f30ec0
commit 7d838375bb
5 changed files with 438 additions and 17 deletions

140
internal/user/debug.go Normal file
View File

@ -0,0 +1,140 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package user
import (
"context"
"fmt"
"strings"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/bradenaw/juniper/xmaps"
"github.com/bradenaw/juniper/xslices"
"github.com/sirupsen/logrus"
)
type DiagnosticMetadata struct {
MessageIDs []string
Metadata []proton.MessageMetadata
FailedMessageIDs xmaps.Set[string]
}
type AccountMailboxMap map[string][]MailboxMessage
type MailboxMessage struct {
ID string
Flags imap.FlagSet
}
func (apm DiagnosticMetadata) BuildMailboxToMessageMap(user *User) (map[string]AccountMailboxMap, error) {
return safe.RLockRetErr(func() (map[string]AccountMailboxMap, error) {
result := make(map[string]AccountMailboxMap)
mode := user.GetAddressMode()
primaryAddrID, err := getPrimaryAddr(user.apiAddrs)
if err != nil {
return nil, fmt.Errorf("failed to get primary addr for user: %w", err)
}
getAccount := func(addrID string) (AccountMailboxMap, bool) {
if mode == vault.CombinedMode {
addrID = primaryAddrID.ID
}
addr := user.apiAddrs[addrID]
if addr.Status != proton.AddressStatusEnabled {
return nil, false
}
v, ok := result[addr.Email]
if !ok {
result[addr.Email] = make(AccountMailboxMap)
v = result[addr.Email]
}
return v, true
}
for _, metadata := range apm.Metadata {
for _, label := range metadata.LabelIDs {
details, ok := user.apiLabels[label]
if !ok {
logrus.Warnf("User %v has message with unknown label '%v'", user.Name(), label)
continue
}
if !wantLabel(details) {
continue
}
account, enabled := getAccount(metadata.AddressID)
if !enabled {
continue
}
var mboxName string
if details.Type == proton.LabelTypeSystem {
mboxName = details.Name
} else {
mboxName = strings.Join(getMailboxName(details), "/")
}
mboxMessage := MailboxMessage{
ID: metadata.ID,
Flags: buildFlagSetFromMessageMetadata(metadata),
}
if v, ok := account[mboxName]; ok {
account[mboxName] = append(v, mboxMessage)
} else {
account[mboxName] = []MailboxMessage{mboxMessage}
}
}
}
return result, nil
}, user.apiAddrsLock, user.apiLabelsLock)
}
func (user *User) GetDiagnosticMetadata(ctx context.Context) (DiagnosticMetadata, error) {
failedMessages := xmaps.SetFromSlice(user.vault.SyncStatus().FailedMessageIDs)
messageIDs, err := user.client.GetMessageIDs(ctx, "")
if err != nil {
return DiagnosticMetadata{}, err
}
meta := make([]proton.MessageMetadata, 0, len(messageIDs))
for _, m := range xslices.Chunk(messageIDs, 100) {
metadata, err := user.client.GetMessageMetadataPage(ctx, 0, len(m), proton.MessageFilter{ID: m})
if err != nil {
return DiagnosticMetadata{}, err
}
meta = append(meta, metadata...)
}
return DiagnosticMetadata{
MessageIDs: messageIDs,
Metadata: meta,
FailedMessageIDs: failedMessages,
}, nil
}