Repository: argoproj/argo
Pull request #4342 (2020-10-22 03:29:03)

[Snyk] Security upgrade chart.js from 2.9.3 to 2.9.4
<h3>Snyk has created this PR to fix one or more vulnerable packages in the `yarn` dependencies of this project.</h3>

![merge advice](https://app.snyk.io/badges/merge-advice/?package_manager=yarn&package_name=chart.js&from_version=2.9.3&to_version=2.9.4&pr_id=7b2556bb-c5bd-4a86-a3d9-49e025fc2906&visibility=true&has_feature_flag=false)


#### Changes included in this PR

- Changes to the following files to upgrade the vulnerable dependencies to a fixed version:
    - ui/package.json
    - ui/yarn.lock



#### Vulnerabilities that will be fixed
##### With an upgrade:
Severity                   | Priority Score (*)                   | Issue                   | Breaking Change                   | Exploit Maturity
:-------------------------:|-------------------------|:-------------------------|:-------------------------|:-------------------------
![high severity](https://res.cloudinary.com/snyk/image/upload/w_20,h_20/v1561977819/icon/h.png "high severity")  |  **768/1000**  <br/> **Why?** Proof of Concept exploit, Recently disclosed, Has a fix available, CVSS 7.5  | Prototype Pollution <br/>[SNYK-JS-CHARTJS-1018716](https://snyk.io/vuln/SNYK-JS-CHARTJS-1018716) |  No  | Proof of Concept 

(*) Note that the real score may have changed since the PR was raised.











Check the changes in this PR to ensure they won't cause issues with your project.



------------



**Note:** *You are seeing this because you or someone else with access to this repository has authorized Snyk to open fix PRs.*

For more information:  <img src="https://api.segment.io/v1/pixel/track?data=eyJ3cml0ZUtleSI6InJyWmxZcEdHY2RyTHZsb0lYd0dUcVg4WkFRTnNCOUEwIiwiYW5vbnltb3VzSWQiOiI3YjI1NTZiYi1jNWJkLTRhODYtYTNkOS00OWUwMjVmYzI5MDYiLCJldmVudCI6IlBSIHZpZXdlZCIsInByb3BlcnRpZXMiOnsicHJJZCI6IjdiMjU1NmJiLWM1YmQtNGE4Ni1hM2Q5LTQ5ZTAyNWZjMjkwNiJ9fQ==" width="0" height="0"/>
🧐 [View latest project report](https://app.snyk.io/org/argoproj-7il/project/ae36157d-9006-45f5-bbaf-edc7abdac9de)

🛠 [Adjust project settings](https://app.snyk.io/org/argoproj-7il/project/ae36157d-9006-45f5-bbaf-edc7abdac9de/settings)

📚 [Read more about Snyk's upgrade and patch logic](https://support.snyk.io/hc/en-us/articles/360003891078-Snyk-patches-to-fix-vulnerabilities)

[//]: # (snyk:metadata:{"prId":"7b2556bb-c5bd-4a86-a3d9-49e025fc2906","dependencies":[{"name":"chart.js","from":"2.9.3","to":"2.9.4"}],"packageManager":"yarn","projectPublicId":"ae36157d-9006-45f5-bbaf-edc7abdac9de","projectUrl":"https://app.snyk.io/org/argoproj-7il/project/ae36157d-9006-45f5-bbaf-edc7abdac9de?utm_source=github&utm_medium=fix-pr","type":"auto","patch":[],"vulns":["SNYK-JS-CHARTJS-1018716"],"upgrade":["SNYK-JS-CHARTJS-1018716"],"isBreakingChange":false,"env":"prod","prType":"fix","templateVariants":["updated-fix-title","priorityScore","merge-advice-badge-shown"],"priorityScoreList":[768]})


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/argoproj/argo?pullRequest=4342) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/argoproj/argo?pullRequest=4342) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/argoproj/argo?pullRequest=4342) it.</sub>

Patch:
diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
index 347473853..594df61bc 100644
--- a/.github/workflows/ci-build.yaml
+++ b/.github/workflows/ci-build.yaml
@@ -17,7 +17,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        test: ["test", "test-e2e", "test-e2e-cron"]
+        test: ["test", "smoke", "test-e2e", "test-e2e-cron"]
     steps:
       - name: Checkout code
         uses: actions/checkout@v2
@@ -74,7 +74,7 @@ jobs:
           GOPATH: /home/runner/go
         run: make ${{ matrix.test }}
       - name: Upload logs
-        if: ${{ failure() && (matrix.test == 'test-e2e' || matrix.test == 'test-e2e-cron') }}
+        if: ${{ always() && matrix.test != 'test' }}
         uses: actions/upload-artifact@v1
         with:
           name: ${{ matrix.test }}-${{ github.run_id }}-argo.log
diff --git a/config/config.go b/config/config.go
index 8967b5acd..b99dbec66 100644
--- a/config/config.go
+++ b/config/config.go
@@ -6,15 +6,12 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
-	"github.com/argoproj/argo/server/auth/sso"
 )
 
-var emptyConfig = Config{}
+var EmptyConfigFunc = func() interface{} { return &Config{} }
 
 // Config contain the configuration settings for the workflow controller
 type Config struct {
-	// SSO in settings for single-sign on
-	SSO sso.Config `json:"sso,omitempty"`
 
 	// NodeEvents configures how node events are omitted
 	NodeEvents NodeEvents `json:"nodeEvents,omitempty"`
@@ -172,7 +169,7 @@ type ConnectionPool struct {
 
 type PostgreSQLConfig struct {
 	Host           string                  `json:"host"`
-	Port           string                  `json:"port"`
+	Port           int                     `json:"port"`
 	Database       string                  `json:"database"`
 	TableName      string                  `json:"tableName,omitempty"`
 	UsernameSecret apiv1.SecretKeySelector `json:"userNameSecret,omitempty"`
@@ -183,7 +180,7 @@ type PostgreSQLConfig struct {
 
 type MySQLConfig struct {
 	Host           string                  `json:"host"`
-	Port           string                  `json:"port"`
+	Port           int                     `json:"port"`
 	Database       string                  `json:"database"`
 	TableName      string                  `json:"tableName,omitempty"`
 	Options        map[string]string       `json:"options,omitempty"`
@@ -249,7 +246,7 @@ type MetricsConfig struct {
 	// Path is the path where metrics are emitted. Must start with a "/". Default is "/metrics"
 	Path string `json:"path,omitempty"`
 	// Port is the port where metrics are emitted. Default is "9090"
-	Port string `json:"port,omitempty"`
+	Port int `json:"port,omitempty"`
 	// IgnoreErrors is a flag that instructs prometheus to ignore metric emission errors
 	IgnoreErrors bool `json:"ignoreErrors,omitempty"`
 }
diff --git a/config/controller.go b/config/controller.go
index 75f797cd4..045eff05d 100644
--- a/config/controller.go
+++ b/config/controller.go
@@ -18,44 +18,46 @@ import (
 )
 
 type Controller interface {
-	Run(stopCh <-chan struct{}, onChange func(config Config) error)
-	Get() (Config, error)
+	Run(stopCh <-chan struct{}, onChange func(config interface{}) error)
+	Get() (interface{}, error)
 }
 
 type controller struct {
 	namespace string
 	// name of the config map
-	configMap     string
-	kubeclientset kubernetes.Interface
+	configMap       string
+	kubeclientset   kubernetes.Interface
+	emptyConfigFunc func() interface{} // must return a pointer, non-nil
 }
 
-func NewController(namespace, name string, kubeclientset kubernetes.Interface) Controller {
+func NewController(namespace, name string, kubeclientset kubernetes.Interface, emptyConfigFunc func() interface{}) Controller {
 	log.WithField("name", name).Info("config map")
 	return &controller{
-		namespace:     namespace,
-		configMap:     name,
-		kubeclientset: kubeclientset,
+		namespace:       namespace,
+		configMap:       name,
+		kubeclientset:   kubeclientset,
+		emptyConfigFunc: emptyConfigFunc,
 	}
 }
 
-func (cc *controller) updateConfig(cm *apiv1.ConfigMap, onChange func(config Config) error) error {
-	config, err := parseConfigMap(cm)
+func (cc *controller) updateConfig(cm *apiv1.ConfigMap, onChange func(config interface{}) error) error {
+	config, err := cc.parseConfigMap(cm)
 	if err != nil {
 		return err
 	}
 	return onChange(config)
 }
 
-func parseConfigMap(cm *apiv1.ConfigMap) (Config, error) {
+func (cc *controller) parseConfigMap(cm *apiv1.ConfigMap) (interface{}, error) {
+	config := cc.emptyConfigFunc()
 	if cm == nil {
-		return emptyConfig, nil
+		return config, nil
 	}
 	// The key in the configmap to retrieve workflow configuration from.
 	// Content encoding is expected to be YAML.
-	var config Config
 	rawConfig, ok := cm.Data["config"]
 	if ok && len(cm.Data) != 1 {
-		return emptyConfig, fmt.Errorf("if you have an item in your config map named 'config', you must only have one item")
+		return config, fmt.Errorf("if you have an item in your config map named 'config', you must only have one item")
 	}
 	if !ok {
 		for name, value := range cm.Data {
@@ -67,14 +69,11 @@ func parseConfigMap(cm *apiv1.ConfigMap) (Config, error) {
 			}
 		}
 	}
-	err := yaml.Unmarshal([]byte(rawConfig), &config)
-	if err != nil {
-		return emptyConfig, err
-	}
-	return config, nil
+	err := yaml.Unmarshal([]byte(rawConfig), config)
+	return config, err
 }
 
-func (cc *controller) Run(stopCh <-chan struct{}, onChange func(config Config) error) {
+func (cc *controller) Run(stopCh <-chan struct{}, onChange func(config interface{}) error) {
 	restClient := cc.kubeclientset.CoreV1().RESTClient()
 	resource := "configmaps"
 	fieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", cc.configMap))
@@ -120,11 +119,11 @@ func (cc *controller) Run(stopCh <-chan struct{}, onChange func(config Config) e
 	log.Info("Watching config map updates")
 }
 
-func (cc *controller) Get() (Config, error) {
+func (cc *controller) Get() (interface{}, error) {
 	cmClient := cc.kubeclientset.CoreV1().ConfigMaps(cc.namespace)
 	cm, err := cmClient.Get(cc.configMap, metav1.GetOptions{})
 	if err != nil && !apierr.IsNotFound(err) {
-		return emptyConfig, err
+		return cc.emptyConfigFunc(), err
 	}
-	return parseConfigMap(cm)
+	return cc.parseConfigMap(cm)
 }
diff --git a/persist/sqldb/sqldb.go b/persist/sqldb/sqldb.go
index 6c1264db2..465192645 100644
--- a/persist/sqldb/sqldb.go
+++ b/persist/sqldb/sqldb.go
@@ -50,7 +50,7 @@ func CreatePostGresDBSession(kubectlConfig kubernetes.Interface, namespace strin
 	var settings = postgresql.ConnectionURL{
 		User:     string(userNameByte),
 		Password: string(passwordByte),
-		Host:     cfg.Host + ":" + cfg.Port,
+		Host:     fmt.Sprintf("%s:%v", cfg.Host, cfg.Port),
 		Database: cfg.Database,
 	}
 
@@ -95,7 +95,7 @@ func CreateMySQLDBSession(kubectlConfig kubernetes.Interface, namespace string,
 	session, err := mysql.Open(mysql.ConnectionURL{
 		User:     string(userNameByte),
 		Password: string(passwordByte),
-		Host:     cfg.Host + ":" + cfg.Port,
+		Host:     fmt.Sprintf("%s:%v", cfg.Host, cfg.Port),
 		Database: cfg.Database,
 	})
 	if err != nil {
diff --git a/server/apiserver/argoserver.go b/server/apiserver/argoserver.go
index 9d78de967..faeb004a4 100644
--- a/server/apiserver/argoserver.go
+++ b/server/apiserver/argoserver.go
@@ -89,14 +89,14 @@ type ArgoServerOpts struct {
 }
 
 func NewArgoServer(opts ArgoServerOpts) (*argoServer, error) {
-	configController := config.NewController(opts.Namespace, opts.ConfigName, opts.KubeClientset)
+	configController := config.NewController(opts.Namespace, opts.ConfigName, opts.KubeClientset, emptyConfigFunc)
 	ssoIf := sso.NullSSO
 	if opts.AuthModes[auth.SSO] {
 		c, err := configController.Get()
 		if err != nil {
 			return nil, err
 		}
-		ssoIf, err = sso.New(c.SSO, opts.KubeClientset.CoreV1().Secrets(opts.Namespace), opts.BaseHRef, opts.TLSConfig != nil)
+		ssoIf, err = sso.New(c.(*Config).SSO, opts.KubeClientset.CoreV1().Secrets(opts.Namespace), opts.BaseHRef, opts.TLSConfig != nil)
 		if err != nil {
 			return nil, err
 		}
@@ -133,15 +133,16 @@ var backoff = wait.Backoff{
 }
 
 func (as *argoServer) Run(ctx context.Context, port int, browserOpenFunc func(string)) {
-	configMap, err := as.configController.Get()
+	v, err := as.configController.Get()
 	if err != nil {
 		log.Fatal(err)
 	}
-	log.WithFields(log.Fields{"version": argo.GetVersion().Version, "instanceID": configMap.InstanceID}).Info("Starting Argo Server")
-	instanceIDService := instanceid.NewService(configMap.InstanceID)
+	config := v.(*Config)
+	log.WithFields(log.Fields{"version": argo.GetVersion().Version, "instanceID": config.InstanceID}).Info("Starting Argo Server")
+	instanceIDService := instanceid.NewService(config.InstanceID)
 	var offloadRepo = sqldb.ExplosiveOffloadNodeStatusRepo
 	var wfArchive = sqldb.NullWorkflowArchive
-	persistence := configMap.Persistence
+	persistence := config.Persistence
 	if persistence != nil {
 		session, tableName, err := sqldb.CreateDBSession(as.kubeClientset, as.namespace, persistence)
 		if err != nil {
@@ -160,7 +161,7 @@ func (as *argoServer) Run(ctx context.Context, port int, browserOpenFunc func(st
 	eventRecorderManager := events.NewEventRecorderManager(as.kubeClientset)
 	artifactServer := artifacts.NewArtifactServer(as.authenticator, hydrator.New(offloadRepo), wfArchive, instanceIDService)
 	eventServer := event.NewController(instanceIDService, eventRecorderManager, as.eventQueueSize, as.eventWorkerCount)
-	grpcServer := as.newGRPCServer(instanceIDService, offloadRepo, wfArchive, eventServer, configMap.Links)
+	grpcServer := as.newGRPCServer(instanceIDService, offloadRepo, wfArchive, eventServer, config.Links)
 	httpServer := as.newHTTPServer(ctx, port, artifactServer)
 
 	// Start listener
@@ -304,7 +305,7 @@ func mustRegisterGWHandler(register registerFunc, ctx context.Context, mux *runt
 // Unlike the controller, the server creates object based on the config map at init time, and will not pick-up on
 // changes unless we restart.
 // Instead of opting to re-write the server, instead we'll just listen for any old change and restart.
-func (as *argoServer) restartOnConfigChange(config.Config) error {
+func (as *argoServer) restartOnConfigChange(interface{}) error {
 	log.Info("config map event, exiting gracefully")
 	as.stopCh <- struct{}{}
 	return nil
diff --git a/server/apiserver/config.go b/server/apiserver/config.go
new file mode 100644
index 000000000..58e1004bb
--- /dev/null
+++ b/server/apiserver/config.go
@@ -0,0 +1,14 @@
+package apiserver
+
+import (
+	"github.com/argoproj/argo/config"
+	"github.com/argoproj/argo/server/auth/sso"
+)
+
+var emptyConfigFunc = func() interface{} { return &Config{} }
+
+type Config struct {
+	config.Config
+	// SSO in settings for single-sign on
+	SSO sso.Config `json:"sso,omitempty"`
+}
diff --git a/ui/package.json b/ui/package.json
index b1a54195c..afe82f33c 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -15,7 +15,7 @@
         "@fortawesome/fontawesome-free": "^5.12.0",
         "@types/dagre": "^0.7.44",
         "argo-ui": "https://github.com/argoproj/argo-ui.git",
-        "chart.js": "^2.9.3",
+        "chart.js": "^2.9.4",
         "chartjs-plugin-annotation": "^0.5.7",
         "classnames": "^2.2.5",
         "cron-parser": "^2.16.3",
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 67a2bfe26..f85cd6ac1 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -1180,7 +1180,7 @@ chalk@^2.0.0, chalk@^2.3.0:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chart.js@^2.4.0, chart.js@^2.9.3:
+chart.js@^2.4.0:
   version "2.9.3"
   resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
   integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
@@ -1188,6 +1188,14 @@ chart.js@^2.4.0, chart.js@^2.9.3:
     chartjs-color "^2.1.0"
     moment "^2.10.2"
 
+chart.js@^2.9.4:
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684"
+  integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==
+  dependencies:
+    chartjs-color "^2.1.0"
+    moment "^2.10.2"
+
 chartjs-color-string@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
diff --git a/workflow/controller/config.go b/workflow/controller/config.go
index 0934b377b..29a7a168c 100644
--- a/workflow/controller/config.go
+++ b/workflow/controller/config.go
@@ -15,7 +15,8 @@ import (
 	"github.com/argoproj/argo/workflow/hydrator"
 )
 
-func (wfc *WorkflowController) updateConfig(config config.Config) error {
+func (wfc *WorkflowController) updateConfig(v interface{}) error {
+	config := v.(*config.Config)
 	bytes, err := yaml.Marshal(config)
 	if err != nil {
 		return err
@@ -24,7 +25,7 @@ func (wfc *WorkflowController) updateConfig(config config.Config) error {
 	if wfc.cliExecutorImage == "" && config.ExecutorImage == "" {
 		return errors.Errorf(errors.CodeBadRequest, "ConfigMap does not have executorImage")
 	}
-	wfc.Config = config
+	wfc.Config = *config
 	if wfc.session != nil {
 		err := wfc.session.Close()
 		if err != nil {
diff --git a/workflow/controller/controller.go b/workflow/controller/controller.go
index 1adedc243..59f9180ef 100644
--- a/workflow/controller/controller.go
+++ b/workflow/controller/controller.go
@@ -125,7 +125,7 @@ func NewWorkflowController(restConfig *rest.Config, kubeclientset kubernetes.Int
 		cliExecutorImage:           executorImage,
 		cliExecutorImagePullPolicy: executorImagePullPolicy,
 		containerRuntimeExecutor:   containerRuntimeExecutor,
-		configController:           config.NewController(namespace, configMap, kubeclientset),
+		configController:           config.NewController(namespace, configMap, kubeclientset, config.EmptyConfigFunc),
 		completedPods:              make(chan string, 512),
 		gcPods:                     make(chan string, 512),
 		workflowKeyLock:            syncpkg.NewKeyLock(),
@@ -719,8 +719,10 @@ func (wfc *WorkflowController) addWorkflowInformerHandlers() {
 			wfc.metrics.WorkflowUpdated(string(wf.GetUID()), getWfPhase(old), getWfPhase(new))
 		},
 		DeleteFunc: func(obj interface{}) {
-			wf := obj.(*unstructured.Unstructured)
-			wfc.metrics.WorkflowDeleted(string(wf.GetUID()), getWfPhase(obj))
+			wf, ok := obj.(*unstructured.Unstructured)
+			if ok { // maybe cache.DeletedFinalStateUnknown
+				wfc.metrics.WorkflowDeleted(string(wf.GetUID()), getWfPhase(obj))
+			}
 		},
 	})
 }
@@ -895,7 +897,7 @@ func (wfc *WorkflowController) getMetricsServerConfig() (metrics.ServerConfig, m
 		path = metrics.DefaultMetricsServerPath
 	}
 	port := wfc.Config.MetricsConfig.Port
-	if port == "" {
+	if port > 0 {
 		port = metrics.DefaultMetricsServerPort
 	}
 	metricsConfig := metrics.ServerConfig{
@@ -913,7 +915,7 @@ func (wfc *WorkflowController) getMetricsServerConfig() (metrics.ServerConfig, m
 	}
 
 	port = metricsConfig.Port
-	if wfc.Config.TelemetryConfig.Port != "" {
+	if wfc.Config.TelemetryConfig.Port > 0 {
 		port = wfc.Config.TelemetryConfig.Port
 	}
 	telemetryConfig := metrics.ServerConfig{
diff --git a/workflow/metrics/metrics.go b/workflow/metrics/metrics.go
index 013198791..97ef4fee5 100644
--- a/workflow/metrics/metrics.go
+++ b/workflow/metrics/metrics.go
@@ -15,14 +15,14 @@ import (
 const (
 	argoNamespace            = "argo"
 	workflowsSubsystem       = "workflows"
-	DefaultMetricsServerPort = "9090"
+	DefaultMetricsServerPort = 9090
 	DefaultMetricsServerPath = "/metrics"
 )
 
 type ServerConfig struct {
 	Enabled      bool
 	Path         string
-	Port         string
+	Port         int
 	TTL          time.Duration
 	IgnoreErrors bool
 }
diff --git a/workflow/metrics/server.go b/workflow/metrics/server.go
index 973332a57..ce06cd3c7 100644
--- a/workflow/metrics/server.go
+++ b/workflow/metrics/server.go
@@ -45,10 +45,10 @@ func runServer(config ServerConfig, registry *prometheus.Registry, ctx context.C
 
 	mux := http.NewServeMux()
 	mux.Handle(config.Path, promhttp.HandlerFor(registry, handlerOpts))
-	srv := &http.Server{Addr: fmt.Sprintf(":%s", config.Port), Handler: mux}
+	srv := &http.Server{Addr: fmt.Sprintf(":%v", config.Port), Handler: mux}
 
 	go func() {
-		log.Infof("Starting prometheus metrics server at localhost:%s%s", config.Port, config.Path)
+		log.Infof("Starting prometheus metrics server at localhost:%v%s", config.Port, config.Path)
 		if err := srv.ListenAndServe(); err != nil {
 			panic(err)
 		}
@@ -61,7 +61,7 @@ func runServer(config ServerConfig, registry *prometheus.Registry, ctx context.C
 	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 	defer cancel()
 	if err := srv.Shutdown(ctx); err != nil {
-		log.Infof("Unable to shutdown metrics server at localhost:%s%s", config.Port, config.Path)
+		log.Infof("Unable to shutdown metrics server at localhost:%v%s", config.Port, config.Path)
 	}
 }
 


Test patch:
diff --git a/config/controller_test.go b/config/controller_test.go
index cbd66694d..8bece7d93 100644
--- a/config/controller_test.go
+++ b/config/controller_test.go
@@ -9,18 +9,19 @@ import (
 )
 
 func Test_parseConfigMap(t *testing.T) {
+	cc := controller{emptyConfigFunc: EmptyConfigFunc}
 	t.Run("Empty", func(t *testing.T) {
-		_, err := parseConfigMap(&apiv1.ConfigMap{})
+		_, err := cc.parseConfigMap(&apiv1.ConfigMap{})
 		assert.NoError(t, err)
 	})
 	t.Run("Config", func(t *testing.T) {
-		c, err := parseConfigMap(&apiv1.ConfigMap{Data: map[string]string{"config": "containerRuntimeExecutor: pns"}})
+		c, err := cc.parseConfigMap(&apiv1.ConfigMap{Data: map[string]string{"config": "containerRuntimeExecutor: pns"}})
 		if assert.NoError(t, err) {
-			assert.Equal(t, "pns", c.ContainerRuntimeExecutor)
+			assert.Equal(t, "pns", c.(*Config).ContainerRuntimeExecutor)
 		}
 	})
 	t.Run("Complex", func(t *testing.T) {
-		c, err := parseConfigMap(&apiv1.ConfigMap{Data: map[string]string{"containerRuntimeExecutor": "pns", "artifactRepository": `    archiveLogs: true
+		c, err := cc.parseConfigMap(&apiv1.ConfigMap{Data: map[string]string{"containerRuntimeExecutor": "pns", "artifactRepository": `    archiveLogs: true
     s3:
       bucket: my-bucket
       endpoint: minio:9000
@@ -32,19 +33,19 @@ func Test_parseConfigMap(t *testing.T) {
         name: my-minio-cred
         key: secretkey`}})
 		if assert.NoError(t, err) {
-			assert.Equal(t, "pns", c.ContainerRuntimeExecutor)
-			assert.NotEmpty(t, c.ArtifactRepository)
+			assert.Equal(t, "pns", c.(*Config).ContainerRuntimeExecutor)
+			assert.NotEmpty(t, c.(*Config).ArtifactRepository)
 		}
 	})
 	t.Run("IgnoreGarbage", func(t *testing.T) {
-		_, err := parseConfigMap(&apiv1.ConfigMap{Data: map[string]string{"garbage": "garbage"}})
+		_, err := cc.parseConfigMap(&apiv1.ConfigMap{Data: map[string]string{"garbage": "garbage"}})
 		assert.NoError(t, err)
 	})
 }
 
 func Test_controller_Get(t *testing.T) {
 	kube := fake.NewSimpleClientset()
-	c := controller{configMap: "my-config-map", kubeclientset: kube}
+	c := controller{configMap: "my-config-map", kubeclientset: kube, emptyConfigFunc: EmptyConfigFunc}
 	config, err := c.Get()
 	if assert.NoError(t, err) {
 		assert.Empty(t, config)
diff --git a/test/e2e/fixtures/persistence.go b/test/e2e/fixtures/persistence.go
index aa81a5d70..8a0d03a2b 100644
--- a/test/e2e/fixtures/persistence.go
+++ b/test/e2e/fixtures/persistence.go
@@ -16,11 +16,12 @@ type Persistence struct {
 }
 
 func newPersistence(kubeClient kubernetes.Interface) *Persistence {
-	configController := config.NewController(Namespace, "workflow-controller-configmap", kubeClient)
-	wcConfig, err := configController.Get()
+	configController := config.NewController(Namespace, "workflow-controller-configmap", kubeClient, config.EmptyConfigFunc)
+	v, err := configController.Get()
 	if err != nil {
 		panic(err)
 	}
+	wcConfig := v.(*config.Config)
 	persistence := wcConfig.Persistence
 	if persistence != nil {
 		if persistence.PostgreSQL != nil {
diff --git a/workflow/controller/config_test.go b/workflow/controller/config_test.go
index bb1e919f5..e266f8281 100644
--- a/workflow/controller/config_test.go
+++ b/workflow/controller/config_test.go
@@ -15,7 +15,7 @@ func TestUpdateConfig(t *testing.T) {
 	cancel, controller := newController()
 	defer cancel()
 	controller.throttler = sync.NewThrottler(0, workqueue.NewNamedRateLimitingQueue(nil, ""))
-	err := controller.updateConfig(config.Config{ExecutorImage: "argoexec:latest"})
+	err := controller.updateConfig(&config.Config{ExecutorImage: "argoexec:latest"})
 	assert.NoError(t, err)
 	assert.NotNil(t, controller.Config)
 	assert.NotNil(t, controller.archiveLabelSelector)
diff --git a/workflow/metrics/metrics_test.go b/workflow/metrics/metrics_test.go
index 569bb6ea8..9557a9f48 100644
--- a/workflow/metrics/metrics_test.go
+++ b/workflow/metrics/metrics_test.go
@@ -32,7 +32,7 @@ func TestServerConfig_SameServerAs(t *testing.T) {
 
 	b.Enabled = true
 	a.Enabled = true
-	b.Port = "9091"
+	b.Port = 9091
 	assert.False(t, a.SameServerAs(b))
 
 	b.Port = DefaultMetricsServerPort