feat(BRIDGE-119): added support for Feature Flags

This commit is contained in:
Atanas Janeshliev
2024-08-07 17:04:54 +02:00
parent 3d53bf7477
commit e290cd308b
11 changed files with 377 additions and 1 deletions

View File

@ -47,6 +47,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
@ -136,6 +137,8 @@ type Bridge struct {
serverManager *imapsmtpserver.Service
syncService *syncservice.Service
// unleashService is responsible for polling the feature flags and caching
unleashService *unleash.Service
}
var logPkg = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
@ -253,6 +256,8 @@ func newBridge(
return nil, fmt.Errorf("failed to create focus service: %w", err)
}
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
bridge := &Bridge{
vault: vault,
@ -293,6 +298,8 @@ func newBridge(
tasks: tasks,
syncService: syncservice.NewService(reporter, panicHandler),
unleashService: unleashService,
}
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
@ -320,6 +327,8 @@ func newBridge(
bridge.syncService.Run()
bridge.unleashService.Run()
return bridge, nil
}
@ -470,6 +479,9 @@ func (bridge *Bridge) Close(ctx context.Context) {
// Close the focus service.
bridge.focusService.Close()
// Close the unleash service.
bridge.unleashService.Close()
// Close the watchers.
bridge.watchersLock.Lock()
defer bridge.watchersLock.Unlock()
@ -674,3 +686,7 @@ func GetUpdatedCachePath(gluonDBPath, gluonCachePath string) string {
return strings.Replace(gluonCachePath, "/Users/"+cacheUsername+"/", "/Users/"+dbUsername+"/", 1)
}
func (bridge *Bridge) GetFeatureFlagValue(key string) bool {
return bridge.unleashService.GetFlagValue(key)
}

View File

@ -33,6 +33,7 @@ type Locator interface {
GetDependencyLicensesLink() string
Clear(...string) error
ProvideIMAPSyncConfigPath() (string, error)
ProvideUnleashCachePath() (string, error)
}
type ProxyController interface {

View File

@ -0,0 +1,90 @@
// Copyright (c) 2024 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 bridge_test
import (
"context"
"testing"
"time"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/go-proton-api/server"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/stretchr/testify/require"
)
func Test_UnleashService(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
unleash.ModifyPollPeriodAndJitter(500*time.Millisecond, 0)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
// Initial startup assumes there is no cached feature flags.
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
s.PushFeatureFlag("test-1")
s.PushFeatureFlag("test-2")
// Wait for poll.
time.Sleep(time.Millisecond * 700)
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
s.PushFeatureFlag("test-3")
time.Sleep(time.Millisecond * 700) // Wait for poll again
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
})
// Wait for Bridge to close.
time.Sleep(time.Millisecond * 500)
// Second instance should have a feature flag cache file available. Therefore, all of the flags should evaluate to true on startup.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
s.DeleteFeatureFlags()
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
time.Sleep(time.Millisecond * 700)
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
s.PushFeatureFlag("test-3")
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
time.Sleep(time.Millisecond * 700)
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
})
})
}