GODT-1917: gRPC service should use random port.

WIP: bridge-gui wait and parse gRPC service config fie.
This commit is contained in:
Xavier Michelon
2022-09-30 11:12:56 +02:00
parent d1cbf4f06c
commit 20c802a1e5
15 changed files with 423 additions and 15 deletions

View File

@ -0,0 +1,65 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 grpc
import (
"encoding/json"
"os"
)
// config is a structure containing the service configuration data that are exchanged by the gRPC server and client.
type config struct {
Port int `json:"port"`
Cert string `json:"cert"` // coming soon
Token string `json:"token"` // coming soon
}
// save saves a gRPC service configuration to file.
func (s *config) save(path string) error {
// Another process may be waiting for this file to be available. In order to prevent this process to open
// the file while we are writing in it, we write it with a temp file name, then rename it.
tempPath := path + "_"
if err := s._save(tempPath); err != nil {
return err
}
return os.Rename(tempPath, path)
}
func (s *config) _save(path string) error {
f, err := os.Create(path) //nolint:errcheck,gosec
if err != nil {
return err
}
defer func() { _ = f.Close() }()
return json.NewEncoder(f).Encode(s)
}
// load loads a gRPC service configuration from file.
func (s *config) load(path string) error {
f, err := os.Open(path) //nolint:errcheck,gosec
if err != nil {
return err
}
defer func() { _ = f.Close() }()
return json.NewDecoder(f).Decode(s)
}

View File

@ -0,0 +1,55 @@
// Copyright (c) 2022 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 grpc
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
const (
dummyPort = 12
dummyCert = "A dummy cert"
dummyToken = "A dummy token"
tempFileName = "test.json"
)
func TestConfig(t *testing.T) {
conf1 := config{
Port: dummyPort,
Cert: dummyCert,
Token: dummyToken,
}
// Read-back test
tempDir := t.TempDir()
tempFilePath := filepath.Join(tempDir, tempFileName)
require.NoError(t, conf1.save(tempFilePath))
conf2 := config{}
require.NoError(t, conf2.load(tempFilePath))
require.Equal(t, conf1, conf2)
// failure to load
require.Error(t, conf2.load(tempFilePath+"_"))
// failure to save
require.Error(t, conf2.save(filepath.Join(tempDir, "non/existing/folder", tempFileName)))
}

View File

@ -22,7 +22,9 @@ package grpc
import (
"context"
cryptotls "crypto/tls"
"fmt"
"net"
"path/filepath"
"strings"
"sync"
"time"
@ -31,6 +33,7 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
@ -43,6 +46,10 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
)
const (
serviceConfigFileName = "grpcServiceConfig.json"
)
// Service is the RPC service struct.
type Service struct { // nolint:structcheck
UnimplementedBridgeServer
@ -68,6 +75,7 @@ type Service struct { // nolint:structcheck
initializing sync.WaitGroup
initializationDone sync.Once
firstTimeAutostart sync.Once
locations *locations.Locations
}
// NewService returns a new instance of the service.
@ -78,6 +86,7 @@ func NewService(
updater types.Updater,
bridge types.Bridger,
restarter types.Restarter,
locations *locations.Locations,
) *Service {
s := Service{
UnimplementedBridgeServer: UnimplementedBridgeServer{},
@ -92,6 +101,7 @@ func NewService(
initializing: sync.WaitGroup{},
initializationDone: sync.Once{},
firstTimeAutostart: sync.Once{},
locations: locations,
}
// Initializing.Done is only called sync.Once. Please keep the increment
@ -111,12 +121,18 @@ func NewService(
RegisterBridgeServer(s.grpcServer, &s)
s.listener, err = net.Listen("tcp", "127.0.0.1:9292") // Port should be configurable from the command-line.
s.listener, err = net.Listen("tcp", "127.0.0.1:0") // Port 0 means that the port is randomly picked by the system.
if err != nil {
s.log.WithError(err).Error("could not create listener")
panic(err)
s.log.WithError(err).Panic("could not create listener")
}
if err := s.saveGRPCServerConfigFile(); err != nil {
s.log.WithError(err).Panic("could not write gRPC service configuration file")
}
s.log.Info("gRPC server listening at ", s.listener.Addr())
return &s
}
@ -387,3 +403,19 @@ func (s *Service) installUpdate() {
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
}
func (s *Service) saveGRPCServerConfigFile() error {
address, ok := s.listener.Addr().(*net.TCPAddr)
if !ok {
return fmt.Errorf("could not retrieve gRPC service listener address")
}
sc := config{Port: address.Port}
settingsPath, err := s.locations.ProvideSettingsPath()
if err != nil {
return err
}
return sc.save(filepath.Join(settingsPath, serviceConfigFileName))
}