diff --git a/cmd/main.go b/cmd/main.go
index c3dd5dd4471..e04f6accdb6 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -312,6 +312,8 @@ func main() {
Client: client,
Scheme: mgr.GetScheme(),
DisableDefaultInstall: strings.ToLower(os.Getenv(common.DisableDefaultInstallEnvVar)) == "true",
+ TLSMinVersion: string(profile.MinTLSVersion),
+ TLSCiphers: profile.Ciphers,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "GitopsService")
os.Exit(1)
diff --git a/controllers/consoleplugin.go b/controllers/consoleplugin.go
index db27fa86f5c..76f1f741a49 100644
--- a/controllers/consoleplugin.go
+++ b/controllers/consoleplugin.go
@@ -2,10 +2,13 @@ package controllers
import (
"context"
+ "crypto/md5"
"fmt"
+ "io"
"os"
"reflect"
"sort"
+ "strings"
argocommon "github.com/argoproj-labs/argocd-operator/common"
argocdutil "github.com/argoproj-labs/argocd-operator/controllers/argoutil"
@@ -240,18 +243,45 @@ func securityContextForPlugin() *corev1.SecurityContext {
}
}
-var httpdConfig = fmt.Sprintf(`LoadModule ssl_module modules/mod_ssl.so
+func TLSVersionToPlugin(tlsVersion string) string {
+ versionMap := map[string]string{
+ "VersionTLS12": "TLSv1.2",
+ "VersionTLS13": "TLSv1.3",
+ "VersionTLS11": "TLSv1.1",
+ "VersionTLS10": "TLSv1",
+ }
+ if v, ok := versionMap[tlsVersion]; ok {
+ return v
+ }
+ return "" // default fallback
+}
+
+// buildHttpdConfig generates httpd.conf with dynamic TLS settings
+func (r *ReconcileGitopsService) buildHttpdConfig() string {
+ httpdConfigBase := fmt.Sprintf(`LoadModule ssl_module modules/mod_ssl.so
Listen %d https
ServerRoot "/etc/httpd"
-
DocumentRoot /var/www/html/plugin
SSLEngine on
SSLCertificateFile "/etc/httpd-ssl/certs/tls.crt"
- SSLCertificateKeyFile "/etc/httpd-ssl/private/tls.key"
-`, servicePort, servicePort)
+ SSLCertificateKeyFile "/etc/httpd-ssl/private/tls.key"`, servicePort, servicePort)
+ // Add SSLProtocol only if explicitly set
+ if TLSVersionToPlugin(r.TLSMinVersion) != "" {
+ httpdConfigBase += fmt.Sprintf("\n\tSSLProtocol %s", TLSVersionToPlugin(r.TLSMinVersion))
+ }
+ if TLSVersionToPlugin(r.TLSMinVersion) != "TLSv1.3" && strings.Join(r.TLSCiphers, ":") != "" {
+ httpdConfigBase += fmt.Sprintf("\n\tSSLCipherSuite %s", strings.Join(r.TLSCiphers, ":"))
+ }
+ // Close VirtualHost
+ httpdConfigBase += "\n"
+ return httpdConfigBase
+}
+
+// pluginConfigMap creates the ConfigMap with dynamic httpd.conf
+func (r *ReconcileGitopsService) pluginConfigMap() *corev1.ConfigMap {
+ httpdConfig := r.buildHttpdConfig()
-func pluginConfigMap() *corev1.ConfigMap {
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: httpdConfigMapName,
@@ -337,7 +367,7 @@ func sortTolerations(tolerations []corev1.Toleration) []corev1.Toleration {
return sorted
}
-func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) {
+func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.GitopsService, request reconcile.Request, newPluginConfigMap *corev1.ConfigMap) (reconcile.Result, error) {
reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
newPluginDeployment := pluginDeployment(cr.Spec.ImagePullPolicy)
@@ -359,6 +389,13 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop
newPluginDeployment.Spec.Template.Spec.Tolerations = cr.Spec.Tolerations
}
+ // ADD THIS: Get ConfigMap and add hash to pod template annotations
+ configMapHash := getConfigMapHash(newPluginConfigMap)
+ if newPluginDeployment.Spec.Template.ObjectMeta.Annotations == nil {
+ newPluginDeployment.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
+ }
+ newPluginDeployment.Spec.Template.ObjectMeta.Annotations["httpd-cfg-hash"] = configMapHash
+
// Check if this Deployment already exists
existingPluginDeployment := &appsv1.Deployment{}
@@ -381,6 +418,7 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop
!equality.Semantic.DeepEqual(existingPluginDeployment.Spec.Replicas, newPluginDeployment.Spec.Replicas) ||
!equality.Semantic.DeepEqual(existingPluginDeployment.Spec.Selector, newPluginDeployment.Spec.Selector) ||
!equality.Semantic.DeepEqual(existingSpecTemplate.Labels, newSpecTemplate.Labels) ||
+ !equality.Semantic.DeepEqual(existingSpecTemplate.ObjectMeta.Annotations["httpd-cfg-hash"], newSpecTemplate.ObjectMeta.Annotations["httpd-cfg-hash"]) ||
!equality.Semantic.DeepEqual(sortContainers(existingSpecTemplate.Spec.Containers), sortContainers(newSpecTemplate.Spec.Containers)) ||
!equality.Semantic.DeepEqual(sortVolumes(existingSpecTemplate.Spec.Volumes), sortVolumes(newSpecTemplate.Spec.Volumes)) ||
!equality.Semantic.DeepEqual(existingSpecTemplate.Spec.RestartPolicy, newSpecTemplate.Spec.RestartPolicy) ||
@@ -396,6 +434,7 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop
existingPluginDeployment.Spec.Replicas = newPluginDeployment.Spec.Replicas
existingPluginDeployment.Spec.Selector = newPluginDeployment.Spec.Selector
existingSpecTemplate.Labels = newSpecTemplate.Labels
+ existingSpecTemplate.ObjectMeta.Annotations["httpd-cfg-hash"] = newSpecTemplate.ObjectMeta.Annotations["httpd-cfg-hash"]
existingSpecTemplate.Spec.SecurityContext = newSpecTemplate.Spec.SecurityContext
existingSpecTemplate.Spec.Containers = newSpecTemplate.Spec.Containers
existingSpecTemplate.Spec.Volumes = newSpecTemplate.Spec.Volumes
@@ -487,9 +526,18 @@ func (r *ReconcileGitopsService) reconcileConsolePlugin(instance *pipelinesv1alp
return reconcile.Result{}, nil
}
-func (r *ReconcileGitopsService) reconcileConfigMap(instance *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) {
+// getConfigMapHash returns MD5 hash of ConfigMap data for change detection
+func getConfigMapHash(cm *corev1.ConfigMap) string {
+ hash := md5.New()
+ for key, value := range cm.Data {
+ io.WriteString(hash, key)
+ io.WriteString(hash, value)
+ }
+ return fmt.Sprintf("%x", hash.Sum(nil))
+}
+
+func (r *ReconcileGitopsService) reconcileConfigMap(instance *pipelinesv1alpha1.GitopsService, request reconcile.Request, newPluginConfigMap *corev1.ConfigMap) (reconcile.Result, error) {
reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
- newPluginConfigMap := pluginConfigMap()
if err := controllerutil.SetControllerReference(instance, newPluginConfigMap, r.Scheme); err != nil {
return reconcile.Result{}, err
@@ -529,15 +577,18 @@ func (r *ReconcileGitopsService) reconcilePlugin(instance *pipelinesv1alpha1.Git
return reconcile.Result{}, nil
}
+ // Generate ConfigMap once
+ newPluginConfigMap := r.pluginConfigMap()
+
if result, err := r.reconcileService(instance, request); err != nil {
return result, err
}
- if result, err := r.reconcileDeployment(instance, request); err != nil {
+ if result, err := r.reconcileDeployment(instance, request, newPluginConfigMap); err != nil {
return result, err
}
- if result, err := r.reconcileConfigMap(instance, request); err != nil {
+ if result, err := r.reconcileConfigMap(instance, request, newPluginConfigMap); err != nil {
return result, err
}
diff --git a/controllers/consoleplugin_test.go b/controllers/consoleplugin_test.go
index 8624f383d89..1e03a3668df 100644
--- a/controllers/consoleplugin_test.go
+++ b/controllers/consoleplugin_test.go
@@ -2,6 +2,8 @@ package controllers
import (
"context"
+ "fmt"
+ "strings"
"testing"
argocommon "github.com/argoproj-labs/argocd-operator/common"
@@ -9,10 +11,12 @@ import (
pipelinesv1alpha1 "github.com/redhat-developer/gitops-operator/api/v1alpha1"
"github.com/redhat-developer/gitops-operator/common"
"gotest.tools/assert"
+ "gotest.tools/assert/cmp"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
resourcev1 "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes/scheme"
@@ -598,7 +602,7 @@ func TestPlugin_reconcileDeployment_changedTemplateLabels(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService(), d).Build()
reconciler := newReconcileGitOpsService(fakeClient, s)
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment := &appsv1.Deployment{}
@@ -643,7 +647,7 @@ func TestPlugin_reconcileDeployment_changedContainers(t *testing.T) {
assert.DeepEqual(t, deployment.Spec.Template.Spec.Containers[0].SecurityContext, securityContextForPlugin())
}
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// There should be a new console plugin deployment created
@@ -668,7 +672,7 @@ func TestPlugin_reconcileDeployment_changedContainers(t *testing.T) {
assertNoError(t, err)
// Verify if the containers are reconciled back to the default values
- _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment = &appsv1.Deployment{}
@@ -975,7 +979,7 @@ func TestPlugin_reconcileDeployment_infraNodeSelectorNotInPodSpec(t *testing.T)
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(gitopsService).Build()
reconciler := newReconcileGitOpsService(fakeClient, s)
- _, err := reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment := &appsv1.Deployment{}
@@ -995,7 +999,7 @@ func TestPlugin_reconcileDeployment(t *testing.T) {
reconciler := newReconcileGitOpsService(fakeClient, s)
instance := &pipelinesv1alpha1.GitopsService{}
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment := &appsv1.Deployment{}
@@ -1032,7 +1036,7 @@ func TestPlugin_reconcileDeployment_ChangedResources(t *testing.T) {
},
}
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment := &appsv1.Deployment{}
@@ -1052,7 +1056,7 @@ func TestPlugin_ReconcileDeployment_DefaultResourceValues(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build()
reconciler := newReconcileGitOpsService(fakeClient, s)
instance := &pipelinesv1alpha1.GitopsService{}
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment := &appsv1.Deployment{}
@@ -1092,7 +1096,7 @@ func TestPlugin_ReconcileDeployment_ChangeExistingResourceValues(t *testing.T) {
Resources: Resources,
},
}
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment := &appsv1.Deployment{}
@@ -1116,7 +1120,7 @@ func TestPlugin_ReconcileDeployment_ChangeExistingResourceValues(t *testing.T) {
}
instance.Spec.ConsolePlugin.Backend.Resources, instance.Spec.ConsolePlugin.GitopsPlugin.Resources = updatedResources, updatedResources
- _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
deployment = &appsv1.Deployment{}
@@ -1589,7 +1593,7 @@ func TestPlug_reconcileConfigMap(t *testing.T) {
reconciler := newReconcileGitOpsService(fakeClient, s)
instance := &pipelinesv1alpha1.GitopsService{}
- _, err := reconciler.reconcileConfigMap(instance, newRequest(serviceNamespace, httpdConfigMapName))
+ _, err := reconciler.reconcileConfigMap(instance, newRequest(serviceNamespace, httpdConfigMapName), reconciler.pluginConfigMap())
assertNoError(t, err)
configMap := &corev1.ConfigMap{}
@@ -1634,7 +1638,7 @@ func TestPlug_reconcileConfigMap_changedLabels(t *testing.T) {
Labels: test.labels,
},
Data: map[string]string{
- "httpd.conf": httpdConfig,
+ "httpd.conf": reconciler.buildHttpdConfig(),
},
}
if x == 0 {
@@ -1665,7 +1669,7 @@ func TestReconcileDeployment_NoUpdateWhenContainersOrderDiffers(t *testing.T) {
instance := &pipelinesv1alpha1.GitopsService{}
// Create deployment
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Get the deployment and capture initial ResourceVersion and Generation
@@ -1688,7 +1692,7 @@ func TestReconcileDeployment_NoUpdateWhenContainersOrderDiffers(t *testing.T) {
}
// Reconcile again - should NOT trigger an update
- _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Verify no update was triggered
@@ -1711,7 +1715,7 @@ func TestReconcileDeployment_NoUpdateWhenVolumesOrderDiffers(t *testing.T) {
instance := &pipelinesv1alpha1.GitopsService{}
// Create deployment
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Get the deployment
@@ -1736,7 +1740,7 @@ func TestReconcileDeployment_NoUpdateWhenVolumesOrderDiffers(t *testing.T) {
genAfterManualUpdate := deployment.Generation
// Reconcile again - should NOT trigger an update
- _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Verify no update was triggered
@@ -1782,7 +1786,7 @@ func TestReconcileDeployment_NoUpdateWhenTolerationsOrderDiffers(t *testing.T) {
reconciler := newReconcileGitOpsService(fakeClient, s)
// Create deployment
- _, err := reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Get the deployment
@@ -1807,7 +1811,7 @@ func TestReconcileDeployment_NoUpdateWhenTolerationsOrderDiffers(t *testing.T) {
genAfterManualUpdate := deployment.Generation
// Reconcile again - should NOT trigger an update
- _, err = reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName))
+ _, err = reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Verify no update was triggered
@@ -1833,7 +1837,7 @@ func TestReconcileDeployment_UpdateWhenActualChange(t *testing.T) {
instance := &pipelinesv1alpha1.GitopsService{}
// Create deployment
- _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Get the deployment and capture initial ResourceVersion and Generation
@@ -1848,7 +1852,7 @@ func TestReconcileDeployment_UpdateWhenActualChange(t *testing.T) {
assertNoError(t, err)
// Reconcile again - should trigger an update
- _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
+ _, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName), reconciler.pluginConfigMap())
assertNoError(t, err)
// Verify update was triggered
@@ -1858,3 +1862,313 @@ func TestReconcileDeployment_UpdateWhenActualChange(t *testing.T) {
assert.Assert(t, deployment.ResourceVersion != initialRV,
"ResourceVersion SHOULD change when actual change is made")
}
+
+func TestTLSVersionToPlugin(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected string
+ }{
+ {
+ name: "TLS 1.2",
+ input: "VersionTLS12",
+ expected: "TLSv1.2",
+ },
+ {
+ name: "TLS 1.3",
+ input: "VersionTLS13",
+ expected: "TLSv1.3",
+ },
+ {
+ name: "TLS 1.1",
+ input: "VersionTLS11",
+ expected: "TLSv1.1",
+ },
+ {
+ name: "TLS 1.0",
+ input: "VersionTLS10",
+ expected: "TLSv1",
+ },
+ {
+ name: "unknown version",
+ input: "VersionTLS14",
+ expected: "",
+ },
+ {
+ name: "empty string",
+ input: "",
+ expected: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ actual := TLSVersionToPlugin(tt.input)
+ if actual != tt.expected {
+ t.Errorf("TLSVersionToPlugin(%q) = %q, want %q", tt.input, actual, tt.expected)
+ }
+ })
+ }
+}
+
+func TestBuildHttpdConfig(t *testing.T) {
+ tests := []struct {
+ name string
+ minVersion string
+ ciphers []string
+ wantProtocol bool
+ wantCipherSuite bool
+ protocol string
+ cipherSuite string
+ }{
+ {
+ name: "no TLS version no ciphers",
+ minVersion: "",
+ ciphers: nil,
+ wantProtocol: false,
+ wantCipherSuite: false,
+ },
+ {
+ name: "TLS 1.2 with cipher suites",
+ minVersion: "VersionTLS12",
+ ciphers: []string{"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-RSA-AES128-GCM-SHA256"},
+ wantProtocol: true,
+ wantCipherSuite: true,
+ protocol: "TLSv1.2",
+ cipherSuite: "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
+ },
+ {
+ name: "TLS 1.3 ignores cipher suites",
+ minVersion: "VersionTLS13",
+ ciphers: []string{"TLS_AES_128_GCM_SHA256"},
+ wantProtocol: true,
+ wantCipherSuite: false,
+ protocol: "TLSv1.3",
+ },
+ {
+ name: "TLS 1.2 without cipher suites",
+ minVersion: "VersionTLS12",
+ ciphers: nil,
+ wantProtocol: true,
+ wantCipherSuite: false,
+ protocol: "TLSv1.2",
+ },
+ {
+ name: "invalid TLS version with cipher suites",
+ minVersion: "invalid",
+ ciphers: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
+ wantProtocol: false,
+ wantCipherSuite: true,
+ cipherSuite: "ECDHE-RSA-AES256-GCM-SHA384",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &ReconcileGitopsService{
+ TLSMinVersion: tt.minVersion,
+ TLSCiphers: tt.ciphers,
+ }
+
+ cfg := r.buildHttpdConfig()
+
+ // Base configuration
+ assert.Assert(t, cmp.Contains(cfg, "LoadModule ssl_module modules/mod_ssl.so"))
+ assert.Assert(t, cmp.Contains(cfg, fmt.Sprintf("Listen %d https", servicePort)))
+ assert.Assert(t, cmp.Contains(cfg, fmt.Sprintf("", servicePort)))
+ assert.Assert(t, cmp.Contains(cfg, `SSLCertificateFile "/etc/httpd-ssl/certs/tls.crt"`))
+ assert.Assert(t, cmp.Contains(cfg, `SSLCertificateKeyFile "/etc/httpd-ssl/private/tls.key"`))
+ assert.Assert(t, cmp.Contains(cfg, ""))
+
+ // SSLProtocol
+ if tt.wantProtocol {
+ assert.Assert(t, cmp.Contains(cfg, "SSLProtocol "+tt.protocol))
+ } else {
+ assert.Assert(t, !strings.Contains(cfg, "SSLProtocol"))
+ }
+
+ // SSLCipherSuite
+ if tt.wantCipherSuite {
+ assert.Assert(t, cmp.Contains(cfg, "SSLCipherSuite "+tt.cipherSuite))
+ } else {
+ assert.Assert(t, !strings.Contains(cfg, "SSLCipherSuite"))
+ }
+ })
+ }
+}
+
+func TestGetConfigMapHash(t *testing.T) {
+ t.Run("same configmap returns same hash", func(t *testing.T) {
+ cm1 := &corev1.ConfigMap{
+ Data: map[string]string{
+ "config": "value",
+ "foo": "bar",
+ },
+ }
+
+ cm2 := &corev1.ConfigMap{
+ Data: map[string]string{
+ "config": "value",
+ "foo": "bar",
+ },
+ }
+
+ hash1 := getConfigMapHash(cm1)
+ hash2 := getConfigMapHash(cm2)
+
+ assert.Equal(t, hash1, hash2)
+ })
+ t.Run("different configmaps return different hashes", func(t *testing.T) {
+ cm1 := &corev1.ConfigMap{
+ Data: map[string]string{
+ "config": "value1",
+ },
+ }
+
+ cm2 := &corev1.ConfigMap{
+ Data: map[string]string{
+ "config": "value2",
+ },
+ }
+
+ hash1 := getConfigMapHash(cm1)
+ hash2 := getConfigMapHash(cm2)
+
+ assert.Assert(t, hash1 != hash2)
+ })
+ t.Run("empty configmap returns deterministic hash", func(t *testing.T) {
+ cm := &corev1.ConfigMap{
+ Data: map[string]string{},
+ }
+
+ hash := getConfigMapHash(cm)
+
+ // MD5 of an empty string.
+ assert.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", hash)
+ })
+}
+
+func TestReconcileDeployment_AddsHashAnnotation(t *testing.T) {
+ gitopsService := "gitops-service"
+ serviceNamespace := "openshift-gitops"
+ scheme := runtime.NewScheme()
+ assert.NilError(t, appsv1.AddToScheme(scheme))
+ assert.NilError(t, corev1.AddToScheme(scheme))
+ assert.NilError(t, pipelinesv1alpha1.AddToScheme(scheme))
+
+ instance := &pipelinesv1alpha1.GitopsService{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: gitopsService,
+ Namespace: serviceNamespace,
+ },
+ }
+
+ cm := &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "plugin-config",
+ Namespace: serviceNamespace,
+ },
+ Data: map[string]string{
+ "httpd.conf": "config-v1",
+ },
+ }
+
+ r := &ReconcileGitopsService{
+ Client: fake.NewClientBuilder().
+ WithScheme(scheme).
+ WithObjects(instance).
+ Build(),
+ Scheme: scheme,
+ }
+
+ _, err := r.reconcileDeployment(
+ instance,
+ newRequest(serviceNamespace, gitopsService),
+ cm,
+ )
+ assert.NilError(t, err)
+
+ deployment := &appsv1.Deployment{}
+ err = r.Client.Get(context.TODO(),
+ types.NamespacedName{
+ Name: gitopsPluginName,
+ Namespace: serviceNamespace,
+ },
+ deployment,
+ )
+ assert.NilError(t, err)
+
+ expectedHash := getConfigMapHash(cm)
+
+ assert.Equal(
+ t,
+ expectedHash,
+ deployment.Spec.Template.Annotations["httpd-cfg-hash"],
+ )
+}
+
+func TestReconcileDeployment_UpdatesHashAnnotationWhenConfigChanges(t *testing.T) {
+ scheme := runtime.NewScheme()
+ gitopsService := "gitops-service"
+ serviceNamespace := "openshift-gitops"
+ assert.NilError(t, appsv1.AddToScheme(scheme))
+ assert.NilError(t, corev1.AddToScheme(scheme))
+ assert.NilError(t, pipelinesv1alpha1.AddToScheme(scheme))
+
+ instance := &pipelinesv1alpha1.GitopsService{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: gitopsService,
+ Namespace: serviceNamespace,
+ },
+ }
+
+ cm1 := &corev1.ConfigMap{
+ Data: map[string]string{
+ "httpd.conf": "config-v1",
+ },
+ }
+
+ cm2 := &corev1.ConfigMap{
+ Data: map[string]string{
+ "httpd.conf": "config-v2",
+ },
+ }
+
+ r := &ReconcileGitopsService{
+ Client: fake.NewClientBuilder().
+ WithScheme(scheme).
+ WithObjects(instance).
+ Build(),
+ Scheme: scheme,
+ }
+
+ _, err := r.reconcileDeployment(
+ instance,
+ newRequest(serviceNamespace, gitopsService),
+ cm1,
+ )
+ assert.NilError(t, err)
+
+ _, err = r.reconcileDeployment(
+ instance,
+ newRequest(serviceNamespace, gitopsService),
+ cm2,
+ )
+ assert.NilError(t, err)
+
+ deployment := &appsv1.Deployment{}
+ err = r.Client.Get(context.TODO(),
+ types.NamespacedName{
+ Name: gitopsPluginName,
+ Namespace: serviceNamespace,
+ },
+ deployment,
+ )
+ assert.NilError(t, err)
+
+ assert.Equal(
+ t,
+ getConfigMapHash(cm2),
+ deployment.Spec.Template.Annotations["httpd-cfg-hash"],
+ )
+}
diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go
index b643cad0529..dac21b6c63f 100644
--- a/controllers/gitopsservice_controller.go
+++ b/controllers/gitopsservice_controller.go
@@ -146,6 +146,10 @@ type ReconcileGitopsService struct {
// disableDefaultInstall, if true, will ensure that the default ArgoCD instance is not instantiated in the openshift-gitops namespace.
DisableDefaultInstall bool
+ // TLSMinVersion is the minimum TLS version to use for the GitOps plugin.
+ TLSMinVersion string
+ // TLSCiphers is the list of supported TLS ciphers for the GitOps plugin.
+ TLSCiphers []string
}
// +kubebuilder:rbac:groups=config.openshift.io,resources=authentications,verbs=get;list;watch