Files
fgueryandGitHub 599477b709 fix(EN-549): remove config and connector dependencies (#622)
* chore: rename RunNextTasks workflow

* feat: added a trimmed down type for connector only containing the id

* feat: make runNextTask use connectorIdOnly

* feat: added isScheduledForDeletion to plugin interface

* feat: add scheduledForDeletion at plugin level

* feat: refactor manager.Load to take a connector instead of parts of it

* feat: saves the connector's scheduledFOrDeletion into the plugin

* feat: use plugin.IsScheduledForDeletion instead of loading connector

* fix: make e2e tests less flaky

* fix: remove config from most request types

* feat: (WIP) remove config from runNextTask

* fix: remove unneeded method definitions in each plugin

* fix: simplify RunNextFlow as anyway we need to change the parameters

* fix: add a workflow compatible with previous version for runNextTasks

* fix(EN-577): added back RunSendEvents as a deprecated workflow

* fix(EN-577): added back RunUpdatePaymentInitiationFromPayment as a deprecated workflow

* fix: fix linting directives

* fix: replace old usage with refactored one

* fix: added back missing deprecated activities

* fix: ensure fetch_next_balance works with v3.0 code

* fix: simplify a bit the versionning etc

* fix: quick refactor before applying solution elsewhere

* fix: add back RunSendEvents and RunNextTask child workflows where necessary for backward compatibility

* fix: add back missing activity, seems like it's mandatory?

* fix: run payment initiation update as child workflow when necessary

* fix: missing activy call in test, refine some todos

* fix: lint

* fix: ensure we always reload plugins into the manager -- so exposing isScheduledForDeletion

* fix: ensure we stop storing clear config in temporal

* fix: make store config support plain text config

* fix: some comments

* fix: change runSendEvent workflow to be match what we had in 3.0

* feat: enable worker versionning to avoid workflow from new version to get executed on old code

* fix: lint

* fix: revert worker versionning

* fix: minor code review comments

* fix: add context to Encrypt & Decrypt methods

* fix: improve lint, add couple tests

* fix: use atomic to handle isScheduledForDeletion

* fix: make create_events work on migration

* fix: avoid returning channel; just send what is needed in runSendEventAsChildWorkflow and runNextTaskAsChildWorkflow

* fix: remove extra call to installWorkflow on start plugin

* fix: typo in error log line

* fix: simplify errChan initialization

* fix: clarify feature flag names
2026-01-05 11:09:45 +00:00

164 lines
4.6 KiB
Go

package connectors
import (
"encoding/json"
"fmt"
"log"
"sync"
"time"
"github.com/formancehq/go-libs/v3/logging"
pluginserrors "github.com/formancehq/payments/internal/connectors/plugins"
"github.com/formancehq/payments/internal/connectors/plugins/registry"
"github.com/formancehq/payments/internal/models"
"github.com/pkg/errors"
)
var (
ErrNotFound = errors.New("plugin not loaded in manager")
ErrValidation = errors.New("validation error")
ErrPollingPeriod = errors.New("polling period invalid")
ErrInvalidOperation = errors.New("invalid operation")
)
//go:generate mockgen -source manager.go -destination manager_generated.go -package connectors . Manager
type Manager interface {
Load(models.Connector, bool, bool) (string, json.RawMessage, error)
Unload(models.ConnectorID)
GetConfig(models.ConnectorID) (models.Config, error)
Get(models.ConnectorID) (models.Plugin, error)
}
// Will start, hold, manage and stop Connectors
type manager struct {
logger logging.Logger
connectors map[string]connector
rwMutex sync.RWMutex
configurer *Configurer
debug bool
}
type connector struct {
plugin models.Plugin
config models.Config
validatedConfigJson json.RawMessage
}
func NewManager(
logger logging.Logger,
debug bool,
pollingPeriodDefault time.Duration,
pollingPeriodMinimum time.Duration,
) *manager {
configurer, err := NewConfigurer(pollingPeriodDefault, pollingPeriodMinimum)
if err != nil {
// NewManager is only expected to be called in modules - we'd rather fail starting the app than
// start it misconfigured
log.Panicf("invalid connector polling period configuration: %v", err)
}
return &manager{
logger: logger,
configurer: configurer,
connectors: make(map[string]connector),
debug: debug,
}
}
func (m *manager) Load(connectorModel models.Connector, updateExisting bool, strictValidation bool) (configName string, validatedConfigJson json.RawMessage, err error) {
m.rwMutex.Lock()
defer m.rwMutex.Unlock()
config := m.configurer.DefaultConfig()
if err := json.Unmarshal(connectorModel.Config, &config); err != nil {
return "", nil, err
}
// Check if plugin is already installed
_, ok := m.connectors[connectorModel.ID.String()]
if ok && !updateExisting {
return config.Name, m.connectors[connectorModel.ID.String()].validatedConfigJson, nil
}
if err := m.configurer.Validate(config); err != nil {
if !errors.Is(err, ErrPollingPeriod) {
return "", nil, err
}
// strict validation takes place on install/update but not when launching a new instance of the app
// which is only loading a presumably already validated value from the DB
if strictValidation {
return "", nil, fmt.Errorf("%w: %w", models.ErrInvalidConfig, err)
}
// if the polling period is lower that the current system default we should still load the plugin
// since creating validation errors will not change the schedule in temporal
m.logger.Errorf("connector %q has a low polling period of %s", connectorModel.ID.String(), config.PollingPeriod)
}
plugin, err := registry.GetPlugin(connectorModel.ID, m.logger, connectorModel.Provider, config.Name, connectorModel.Config)
switch {
case errors.Is(err, pluginserrors.ErrNotImplemented),
errors.Is(err, pluginserrors.ErrInvalidClientRequest):
return "", nil, fmt.Errorf("%w: %w", err, ErrValidation)
case err != nil:
return "", nil, err
}
b, err := combineConfigs(config, plugin.Config())
if err != nil {
return "", nil, fmt.Errorf("failed to combine configs: %w", err)
}
plugin.ScheduleForDeletion(connectorModel.ScheduledForDeletion)
validatedConfigJson = json.RawMessage(b)
m.connectors[connectorModel.ID.String()] = connector{
plugin: plugin,
config: config,
validatedConfigJson: validatedConfigJson,
}
return config.Name, validatedConfigJson, nil
}
func (m *manager) Unload(connectorID models.ConnectorID) {
m.rwMutex.Lock()
defer m.rwMutex.Unlock()
_, ok := m.connectors[connectorID.String()]
if !ok {
// Nothing to do
return
}
delete(m.connectors, connectorID.String())
}
func (m *manager) Get(connectorID models.ConnectorID) (models.Plugin, error) {
m.rwMutex.RLock()
defer m.rwMutex.RUnlock()
c, ok := m.connectors[connectorID.String()]
if !ok {
return nil, fmt.Errorf("%s: %w", connectorID.String(), ErrNotFound)
}
return c.plugin, nil
}
func (m *manager) GetConfig(connectorID models.ConnectorID) (models.Config, error) {
m.rwMutex.RLock()
defer m.rwMutex.RUnlock()
c, ok := m.connectors[connectorID.String()]
if !ok {
return models.Config{}, fmt.Errorf("%s: %w", connectorID.String(), ErrNotFound)
}
return c.config, nil
}
var _ Manager = &manager{}