feat(extensions): Automatically apply extension configs without restarting API-Server (#15574)

* feat: auto configure extensions

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* feat: auto-reload extension configs without restarting api-server

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* clean unused gorilla mux

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* update docs

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Address review comments

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Add more test cases

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* refactoring to reduce unnecessary function

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* Add log

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

* fix bugs found during manual tests

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

---------

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
This commit is contained in:
Leonardo Luz Almeida 2023-09-21 16:57:09 -04:00 committed by GitHub
parent 98ee9443e3
commit ef88d1d026
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 203 additions and 117 deletions

View file

@ -32,7 +32,7 @@ data:
Once the proxy extension is enabled, it can be configured in the main
Argo CD configmap ([argocd-cm][2]).
The example below demonstrate all possible configurations available
The example below demonstrates all possible configurations available
for proxy extensions:
```yaml
@ -60,9 +60,11 @@ data:
server: https://some-cluster
```
If a the configuration is changed, Argo CD Server will need to be
restarted as the proxy handlers are only registered once during the
initialization of the server.
Note: There is no need to restart Argo CD Server after modifiying the
`extension.config` entry in Argo CD configmap. Changes will be
automatically applied. A new proxy registry will be built making
all new incoming extensions requests (`<argocd-host>/extensions/*`) to
respect the new configuration.
Every configuration entry is explained below:

3
go.mod
View file

@ -42,7 +42,6 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/gosimple/slug v1.13.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
@ -88,6 +87,7 @@ require (
google.golang.org/protobuf v1.31.0
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.24.2
k8s.io/apiextensions-apiserver v0.24.2
k8s.io/apimachinery v0.24.2
@ -274,7 +274,6 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/cli-runtime v0.24.2 // indirect
k8s.io/component-base v0.24.2 // indirect
k8s.io/component-helpers v0.24.2 // indirect

1
go.sum
View file

@ -1277,7 +1277,6 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=

View file

@ -12,9 +12,8 @@ import (
"strings"
"time"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/yaml"
"gopkg.in/yaml.v3"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
@ -126,15 +125,15 @@ func getAppName(appHeader string) (string, string, error) {
// ExtensionConfigs defines the configurations for all extensions
// retrieved from Argo CD configmap (argocd-cm).
type ExtensionConfigs struct {
Extensions []ExtensionConfig `json:"extensions"`
Extensions []ExtensionConfig `yaml:"extensions"`
}
// ExtensionConfig defines the configuration for one extension.
type ExtensionConfig struct {
// Name defines the endpoint that will be used to register
// the extension route. Mandatory field.
Name string `json:"name"`
Backend BackendConfig `json:"backend"`
Name string `yaml:"name"`
Backend BackendConfig `yaml:"backend"`
}
// BackendConfig defines the backend service configurations that will
@ -144,30 +143,30 @@ type ExtensionConfig struct {
// service.
type BackendConfig struct {
ProxyConfig
Services []ServiceConfig `json:"services"`
Services []ServiceConfig `yaml:"services"`
}
// ServiceConfig provides the configuration for a backend service.
type ServiceConfig struct {
// URL is the address where the extension backend must be available.
// Mandatory field.
URL string `json:"url"`
URL string `yaml:"url"`
// Cluster if provided, will have to match the application
// destination name to have requests properly forwarded to this
// service URL.
Cluster *ClusterConfig `json:"cluster,omitempty"`
Cluster *ClusterConfig `yaml:"cluster,omitempty"`
// Headers if provided, the headers list will be added on all
// outgoing requests for this service config.
Headers []Header `json:"headers"`
Headers []Header `yaml:"headers"`
}
// Header defines the header to be added in the proxy requests.
type Header struct {
// Name defines the name of the header. It is a mandatory field if
// a header is provided.
Name string `json:"name"`
Name string `yaml:"name"`
// Value defines the value of the header. The actual value can be
// provided as verbatim or as a reference to an Argo CD secret key.
// In order to provide it as a reference, it is necessary to prefix
@ -176,15 +175,15 @@ type Header struct {
// value: '$some.argocd.secret.key'
// In the example above, the value will be replaced with the one from
// the argocd-secret with key 'some.argocd.secret.key'.
Value string `json:"value"`
Value string `yaml:"value"`
}
type ClusterConfig struct {
// Server specifies the URL of the target cluster's Kubernetes control plane API. This must be set if Name is not set.
Server string `json:"server"`
Server string `yaml:"server"`
// Name is an alternate way of specifying the target cluster by its symbolic name. This must be set if Server is not set.
Name string `json:"name"`
Name string `yaml:"name"`
}
// ProxyConfig allows configuring connection behaviour between Argo CD
@ -193,24 +192,24 @@ type ProxyConfig struct {
// ConnectionTimeout is the maximum amount of time a dial to
// the extension server will wait for a connect to complete.
// Default: 2 seconds
ConnectionTimeout time.Duration `json:"connectionTimeout"`
ConnectionTimeout time.Duration `yaml:"connectionTimeout"`
// KeepAlive specifies the interval between keep-alive probes
// for an active network connection between the API server and
// the extension server.
// Default: 15 seconds
KeepAlive time.Duration `json:"keepAlive"`
KeepAlive time.Duration `yaml:"keepAlive"`
// IdleConnectionTimeout is the maximum amount of time an idle
// (keep-alive) connection between the API server and the extension
// server will remain idle before closing itself.
// Default: 60 seconds
IdleConnectionTimeout time.Duration `json:"idleConnectionTimeout"`
IdleConnectionTimeout time.Duration `yaml:"idleConnectionTimeout"`
// MaxIdleConnections controls the maximum number of idle (keep-alive)
// connections between the API server and the extension server.
// Default: 30
MaxIdleConnections int `json:"maxIdleConnections"`
MaxIdleConnections int `yaml:"maxIdleConnections"`
}
// SettingsGetter defines the contract to retrieve Argo CD Settings.
@ -300,6 +299,7 @@ type Manager struct {
application ApplicationGetter
project ProjectGetter
rbac RbacEnforcer
registry ExtensionRegistry
}
// NewManager will initialize a new manager.
@ -313,6 +313,11 @@ func NewManager(log *log.Entry, sg SettingsGetter, ag ApplicationGetter, pg Proj
}
}
// ExtensionRegistry is an in memory registry that contains contains all
// proxies for all extensions. The key is the extension name defined in
// the Argo CD configmap.
type ExtensionRegistry map[string]ProxyRegistry
// ProxyRegistry is an in memory registry that contains all proxies for a
// given extension. Different extensions will have independent proxy registries.
// This is required to address the use case when one extension is configured with
@ -344,6 +349,10 @@ func proxyKey(extName, cName, cServer string) ProxyKey {
}
func parseAndValidateConfig(s *settings.ArgoCDSettings) (*ExtensionConfigs, error) {
if s.ExtensionConfig == "" {
return nil, fmt.Errorf("no extensions configurations found")
}
extConfigMap := map[string]interface{}{}
err := yaml.Unmarshal([]byte(s.ExtensionConfig), &extConfigMap)
if err != nil {
@ -383,6 +392,9 @@ func validateConfigs(configs *ExtensionConfigs) error {
}
exts[ext.Name] = struct{}{}
svcTotal := len(ext.Backend.Services)
if svcTotal == 0 {
return fmt.Errorf("no backend service configured for extension %s", ext.Name)
}
for _, svc := range ext.Backend.Services {
if svc.URL == "" {
return fmt.Errorf("extensions.backend.services.url must be configured")
@ -465,25 +477,47 @@ func applyProxyConfigDefaults(c *ProxyConfig) {
}
}
// RegisterHandlers will retrieve all configured extensions
// and register the respective http handlers in the given
// router.
func (m *Manager) RegisterHandlers(r *mux.Router) error {
m.log.Info("Registering extension handlers...")
// RegisterExtensions will retrieve all extensions configurations
// and update the extension registry.
func (m *Manager) RegisterExtensions() error {
settings, err := m.settings.Get()
if err != nil {
return fmt.Errorf("error getting settings: %s", err)
}
if settings.ExtensionConfig == "" {
return fmt.Errorf("No extensions configurations found")
err = m.UpdateExtensionRegistry(settings)
if err != nil {
return fmt.Errorf("error updating extension registry: %s", err)
}
return nil
}
extConfigs, err := parseAndValidateConfig(settings)
// UpdateExtensionRegistry will first parse and validate the extensions
// configurations from the given settings. If no errors are found, it will
// iterate over the given configurations building a new extension registry.
// At the end, it will update the manager with the newly created registry.
func (m *Manager) UpdateExtensionRegistry(s *settings.ArgoCDSettings) error {
extConfigs, err := parseAndValidateConfig(s)
if err != nil {
return fmt.Errorf("error parsing extension config: %s", err)
}
return m.registerExtensions(r, extConfigs)
extReg := make(map[string]ProxyRegistry)
for _, ext := range extConfigs.Extensions {
proxyReg := NewProxyRegistry()
singleBackend := len(ext.Backend.Services) == 1
for _, service := range ext.Backend.Services {
proxy, err := NewProxy(service.URL, service.Headers, ext.Backend.ProxyConfig)
if err != nil {
return fmt.Errorf("error creating proxy: %s", err)
}
err = appendProxy(proxyReg, ext.Name, service, proxy, singleBackend)
if err != nil {
return fmt.Errorf("error appending proxy: %s", err)
}
}
extReg[ext.Name] = proxyReg
}
m.registry = extReg
return nil
}
// appendProxy will append the given proxy in the given registry. Will use
@ -525,31 +559,6 @@ func appendProxy(registry ProxyRegistry,
return nil
}
// registerExtensions will iterate over the given extConfigs and register
// http handlers for every extension. It also registers a list extensions
// handler under the "/extensions/" endpoint.
func (m *Manager) registerExtensions(r *mux.Router, extConfigs *ExtensionConfigs) error {
extRouter := r.PathPrefix(fmt.Sprintf("%s/", URLPrefix)).Subrouter()
for _, ext := range extConfigs.Extensions {
registry := NewProxyRegistry()
singleBackend := len(ext.Backend.Services) == 1
for _, service := range ext.Backend.Services {
proxy, err := NewProxy(service.URL, service.Headers, ext.Backend.ProxyConfig)
if err != nil {
return fmt.Errorf("error creating proxy: %s", err)
}
err = appendProxy(registry, ext.Name, service, proxy, singleBackend)
if err != nil {
return fmt.Errorf("error appending proxy: %s", err)
}
}
m.log.Infof("Registering handler for %s/%s...", URLPrefix, ext.Name)
extRouter.PathPrefix(fmt.Sprintf("/%s/", ext.Name)).
HandlerFunc(m.CallExtension(ext.Name, registry))
}
return nil
}
// authorize will enforce rbac rules are satified for the given RequestResources.
// The following validations are executed:
// - enforce the subject has permission to read application/project provided
@ -624,10 +633,29 @@ func findProxy(registry ProxyRegistry, extName string, dest v1alpha1.Application
return nil, fmt.Errorf("no proxy found for extension %q", extName)
}
// ProxyRegistry returns the proxy registry associated for the given
// extension name.
func (m *Manager) ProxyRegistry(name string) (ProxyRegistry, bool) {
pReg, found := m.registry[name]
return pReg, found
}
// CallExtension returns a handler func responsible for forwarding requests to the
// extension service. The request will be sanitized by removing sensitive headers.
func (m *Manager) CallExtension(extName string, registry ProxyRegistry) func(http.ResponseWriter, *http.Request) {
func (m *Manager) CallExtension() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
segments := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")
if segments[0] != "extensions" {
http.Error(w, fmt.Sprintf("Invalid URL: first segment must be %s", URLPrefix), http.StatusBadRequest)
return
}
extName := segments[1]
if extName == "" {
http.Error(w, "Invalid URL: extension name must be provided", http.StatusBadRequest)
return
}
extName = strings.ReplaceAll(extName, "\n", "")
extName = strings.ReplaceAll(extName, "\r", "")
reqResources, err := ValidateHeaders(r)
if err != nil {
http.Error(w, fmt.Sprintf("Invalid headers: %s", err), http.StatusBadRequest)
@ -640,7 +668,13 @@ func (m *Manager) CallExtension(extName string, registry ProxyRegistry) func(htt
return
}
proxy, err := findProxy(registry, extName, app.Spec.Destination)
proxyRegistry, ok := m.ProxyRegistry(extName)
if !ok {
m.log.Warnf("proxy extension warning: attempt to call unregistered extension: %s", extName)
http.Error(w, "Extension not found", http.StatusNotFound)
return
}
proxy, err := findProxy(proxyRegistry, extName, app.Spec.Destination)
if err != nil {
m.log.Errorf("findProxy error: %s", err)
http.Error(w, "invalid extension", http.StatusBadRequest)

View file

@ -10,7 +10,6 @@ import (
"strings"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@ -139,7 +138,7 @@ func TestValidateHeaders(t *testing.T) {
})
}
func TestRegisterHandlers(t *testing.T) {
func TestRegisterExtensions(t *testing.T) {
type fixture struct {
settingsGetterMock *mocks.SettingsGetter
manager *extension.Manager
@ -157,34 +156,29 @@ func TestRegisterHandlers(t *testing.T) {
manager: m,
}
}
t.Run("will register handlers successfully", func(t *testing.T) {
t.Run("will register extensions successfully", func(t *testing.T) {
// given
t.Parallel()
f := setup()
router := mux.NewRouter()
settings := &settings.ArgoCDSettings{
ExtensionConfig: getExtensionConfigString(),
}
f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
expectedRegexRoutes := []string{
"^/extensions/",
"^/extensions/external-backend/",
"^/extensions/some-backend/",
"^/extensions/$"}
expectedProxyRegistries := []string{
"external-backend",
"some-backend"}
// when
err := f.manager.RegisterHandlers(router)
err := f.manager.RegisterExtensions()
// then
require.NoError(t, err)
walkFn := func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
pathRegex, err := route.GetPathRegexp()
require.NoError(t, err)
assert.Contains(t, expectedRegexRoutes, pathRegex)
return nil
for _, expectedProxyRegistry := range expectedProxyRegistries {
proxyRegistry, found := f.manager.ProxyRegistry(expectedProxyRegistry)
assert.True(t, found)
assert.NotNil(t, proxyRegistry)
}
err = router.Walk(walkFn)
assert.NoError(t, err)
})
t.Run("will return error if extension config is invalid", func(t *testing.T) {
// given
@ -202,6 +196,10 @@ func TestRegisterHandlers(t *testing.T) {
name: "no name",
configYaml: getExtensionConfigNoName(),
},
{
name: "no service",
configYaml: getExtensionConfigNoService(),
},
{
name: "no URL",
configYaml: getExtensionConfigNoURL(),
@ -227,14 +225,13 @@ func TestRegisterHandlers(t *testing.T) {
// given
t.Parallel()
f := setup()
router := mux.NewRouter()
settings := &settings.ArgoCDSettings{
ExtensionConfig: tc.configYaml,
}
f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
// when
err := f.manager.RegisterHandlers(router)
err := f.manager.RegisterExtensions()
// then
assert.Error(t, err)
@ -243,9 +240,9 @@ func TestRegisterHandlers(t *testing.T) {
})
}
func TestExtensionsHandler(t *testing.T) {
func TestCallExtension(t *testing.T) {
type fixture struct {
router *mux.Router
mux *http.ServeMux
appGetterMock *mocks.ApplicationGetter
settingsGetterMock *mocks.SettingsGetter
rbacMock *mocks.RbacEnforcer
@ -264,10 +261,12 @@ func TestExtensionsHandler(t *testing.T) {
logEntry := logger.WithContext(context.Background())
m := extension.NewManager(logEntry, settMock, appMock, projMock, rbacMock)
router := mux.NewRouter()
mux := http.NewServeMux()
extHandler := http.HandlerFunc(m.CallExtension())
mux.Handle(fmt.Sprintf("%s/", extension.URLPrefix), extHandler)
return &fixture{
router: router,
mux: mux,
appGetterMock: appMock,
settingsGetterMock: settMock,
rbacMock: rbacMock,
@ -356,11 +355,11 @@ func TestExtensionsHandler(t *testing.T) {
startTestServer := func(t *testing.T, f *fixture) *httptest.Server {
t.Helper()
err := f.manager.RegisterHandlers(f.router)
err := f.manager.RegisterExtensions()
if err != nil {
t.Fatalf("error starting test server: %s", err)
}
return httptest.NewServer(f.router)
return httptest.NewServer(f.mux)
}
startBackendTestSrv := func(response string) *httptest.Server {
@ -383,23 +382,6 @@ func TestExtensionsHandler(t *testing.T) {
return r
}
t.Run("proxy will return 404 if no extension endpoint is registered", func(t *testing.T) {
// given
t.Parallel()
f := setup()
withExtensionConfig(getExtensionConfigString(), f)
ts := startTestServer(t, f)
defer ts.Close()
nonRegisteredEndpoint := "non-registered"
// when
resp, err := http.Get(fmt.Sprintf("%s/extensions/%s/", ts.URL, nonRegisteredEndpoint))
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("will call extension backend successfully", func(t *testing.T) {
// given
t.Parallel()
@ -439,6 +421,29 @@ func TestExtensionsHandler(t *testing.T) {
assert.Equal(t, clusterURL, resp.Header.Get(extension.HeaderArgoCDTargetClusterURL))
assert.Equal(t, "Bearer some-bearer-token", resp.Header.Get("Authorization"))
})
t.Run("proxy will return 404 if extension endpoint not registered", func(t *testing.T) {
// given
t.Parallel()
f := setup()
withExtensionConfig(getExtensionConfigString(), f)
withRbac(f, true, true)
cluster1Name := "cluster1"
f.appGetterMock.On("Get", "namespace", "app-name").Return(getApp(cluster1Name, "", defaultProjectName), nil)
withProject(getProjectWithDestinations("project-name", []string{cluster1Name}, []string{"some-url"}), f)
ts := startTestServer(t, f)
defer ts.Close()
nonRegistered := "non-registered"
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/%s/", ts.URL, nonRegistered))
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("will route requests with 2 backends for the same extension successfully", func(t *testing.T) {
// given
t.Parallel()
@ -651,6 +656,29 @@ func TestExtensionsHandler(t *testing.T) {
actual := strings.TrimSuffix(string(body), "\n")
assert.Equal(t, "invalid extension", actual)
})
t.Run("will return 400 if no extension name is provided", func(t *testing.T) {
// given
t.Parallel()
f := setup()
allowApp := true
allowExtension := true
extName := "some-extension"
differentProject := "differentProject"
withRbac(f, allowApp, allowExtension)
withExtensionConfig(getExtensionConfig(extName, "http://fake"), f)
ts := startTestServer(t, f)
defer ts.Close()
r := newExtensionRequest(t, "Get", fmt.Sprintf("%s/extensions/", ts.URL))
f.appGetterMock.On("Get", mock.Anything, mock.Anything).Return(getApp("", "", differentProject), nil)
// when
resp, err := http.DefaultClient.Do(r)
// then
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
}
func getExtensionConfig(name, url string) string {
@ -697,6 +725,10 @@ func getExtensionConfigString() string {
extensions:
- name: external-backend
backend:
connectionTimeout: 10s
keepAlive: 11s
idleConnectionTimeout: 12s
maxIdleConnections: 30
services:
- url: https://httpbin.org
headers:
@ -709,6 +741,13 @@ extensions:
`
}
func getExtensionConfigNoService() string {
return `
extensions:
- backend:
connectionTimeout: 2s
`
}
func getExtensionConfigNoName() string {
return `
extensions:

View file

@ -31,7 +31,6 @@ import (
"github.com/argoproj/pkg/sync"
"github.com/golang-jwt/jwt/v4"
"github.com/gorilla/handlers"
gmux "github.com/gorilla/mux"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
@ -193,6 +192,7 @@ type ArgoCDServer struct {
secretInformer cache.SharedIndexInformer
configMapInformer cache.SharedIndexInformer
serviceSet *ArgoCDServiceSet
extensionManager *extension.Manager
}
type ArgoCDServerOpts struct {
@ -291,10 +291,16 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
apiFactory := api.NewFactory(settings_notif.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm"), opts.Namespace, secretInformer, configMapInformer)
dbInstance := db.NewDB(opts.Namespace, settingsMgr, opts.KubeClientset)
logger := log.NewEntry(log.StandardLogger())
sg := extension.NewDefaultSettingsGetter(settingsMgr)
ag := extension.NewDefaultApplicationGetter(appLister)
pg := extension.NewDefaultProjectGetter(projLister, dbInstance)
em := extension.NewManager(logger, sg, ag, pg, enf)
a := &ArgoCDServer{
ArgoCDServerOpts: opts,
log: log.NewEntry(log.StandardLogger()),
log: logger,
settings: settings,
sessionMgr: sessionMgr,
settingsMgr: settingsMgr,
@ -312,6 +318,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
apiFactory: apiFactory,
secretInformer: secretInformer,
configMapInformer: configMapInformer,
extensionManager: em,
}
err = a.logInClusterWarnings()
@ -616,6 +623,7 @@ func (a *ArgoCDServer) watchSettings() {
prevBitbucketUUID := a.settings.WebhookBitbucketUUID
prevBitbucketServerSecret := a.settings.WebhookBitbucketServerSecret
prevGogsSecret := a.settings.WebhookGogsSecret
prevExtConfig := a.settings.ExtensionConfig
var prevCert, prevCertKey string
if a.settings.Certificate != nil && !a.ArgoCDServerOpts.Insecure {
prevCert, prevCertKey = tlsutil.EncodeX509KeyPairString(*a.settings.Certificate)
@ -658,6 +666,16 @@ func (a *ArgoCDServer) watchSettings() {
log.Infof("gogs secret modified. restarting")
break
}
if prevExtConfig != a.settings.ExtensionConfig {
prevExtConfig = a.settings.ExtensionConfig
log.Infof("extensions configs modified. Updating proxy registry...")
err := a.extensionManager.UpdateExtensionRegistry(a.settings)
if err != nil {
log.Errorf("error updating extensions configs: %s", err)
} else {
log.Info("extensions configs updated successfully")
}
}
if !a.ArgoCDServerOpts.Insecure {
var newCert, newCertKey string
if a.settings.Certificate != nil {
@ -1042,21 +1060,16 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
// in the given mux. If any error is returned while registering
// extensions handlers, no route will be added in the given mux.
func registerExtensions(mux *http.ServeMux, a *ArgoCDServer) {
sg := extension.NewDefaultSettingsGetter(a.settingsMgr)
ag := extension.NewDefaultApplicationGetter(a.appLister)
pg := extension.NewDefaultProjectGetter(a.projLister, a.db)
em := extension.NewManager(a.log, sg, ag, pg, a.enf)
r := gmux.NewRouter()
// register an Auth middleware to ensure all requests to
// extensions are authenticated first.
r.Use(a.sessionMgr.AuthMiddlewareFunc(a.DisableAuth))
a.log.Info("Registering extensions...")
extHandler := http.HandlerFunc(a.extensionManager.CallExtension())
authMiddleware := a.sessionMgr.AuthMiddlewareFunc(a.DisableAuth)
// auth middleware ensures that requests to all extensions are authenticated first
mux.Handle(fmt.Sprintf("%s/", extension.URLPrefix), authMiddleware(extHandler))
err := em.RegisterHandlers(r)
err := a.extensionManager.RegisterExtensions()
if err != nil {
a.log.Errorf("error registering extension handlers: %s", err)
return
a.log.Errorf("Error registering extensions: %s", err)
}
mux.Handle(fmt.Sprintf("%s/", extension.URLPrefix), r)
}
var extensionsPattern = regexp.MustCompile(`^extension(.*)\.js$`)