Skip to content

Commit eda09f2

Browse files
committed
Postgres reconciliation on certificate rotation.
#Updating hash trigger.
1 parent 09d48f0 commit eda09f2

File tree

7 files changed

+153
-37
lines changed

7 files changed

+153
-37
lines changed

internal/controller/constants.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ const (
169169
// PostgresSecretHashKey is the key of the hash value of OLS Postgres secret
170170
// #nosec G101
171171
PostgresSecretHashKey = "hash/postgres-secret"
172+
// PostgresCAHashKey is the key of the hash value of the OLS Postgres CA certificate
173+
PostgresCAHashKey = "hash/postgres-ca"
174+
// PostgresServiceCACertKeyName is the data key name for the service CA certificate in the ConfigMap
175+
PostgresServiceCACertKeyName = "service-ca.crt"
176+
// PostgresTLSCertKeyName is the data key name for the TLS certificate in the Secret
177+
PostgresTLSCertKeyName = "tls.crt"
172178
// PostgresServiceName is the name of OLS application Postgres server service
173179
PostgresServiceName = "lightspeed-postgres-server"
174180
// PostgresSecretName is the name of OLS application Postgres secret
@@ -255,6 +261,7 @@ ssl_ca_file = '/etc/certs/cm-olspostgresca/service-ca.crt'
255261
OperatorDeploymentName = "lightspeed-operator-controller-manager"
256262
OLSDefaultCacheType = "postgres"
257263
PostgresConfigHashStateCacheKey = "olspostgresconfig-hash"
264+
PostgresCAHashStateCacheKey = "olspostgresca-hash"
258265
// #nosec G101
259266
PostgresSecretHashStateCacheKey = "olspostgressecret-hash"
260267
// OperatorNetworkPolicyName is the name of the network policy for the operator

internal/controller/ols_app_postgres_assets.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,16 +256,16 @@ func (r *OLSConfigReconciler) updatePostgresDeployment(ctx context.Context, exis
256256
// Validate deployment annotations.
257257
if existingDeployment.Annotations == nil ||
258258
existingDeployment.Annotations[PostgresConfigHashKey] != r.stateCache[PostgresConfigHashStateCacheKey] ||
259-
existingDeployment.Annotations[PostgresSecretHashKey] != r.stateCache[PostgresSecretHashStateCacheKey] {
260-
updateDeploymentAnnotations(existingDeployment, map[string]string{
259+
existingDeployment.Annotations[PostgresSecretHashKey] != r.stateCache[PostgresSecretHashStateCacheKey] ||
260+
existingDeployment.Annotations[PostgresCAHashKey] != r.stateCache[PostgresCAHashStateCacheKey] {
261+
annotations := map[string]string{
261262
PostgresConfigHashKey: r.stateCache[PostgresConfigHashStateCacheKey],
262263
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
263-
})
264+
PostgresCAHashKey: r.stateCache[PostgresCAHashStateCacheKey],
265+
}
266+
updateDeploymentAnnotations(existingDeployment, annotations)
264267
// update the deployment template annotation triggers the rolling update
265-
updateDeploymentTemplateAnnotations(existingDeployment, map[string]string{
266-
PostgresConfigHashKey: r.stateCache[PostgresConfigHashStateCacheKey],
267-
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
268-
})
268+
updateDeploymentTemplateAnnotations(existingDeployment, annotations)
269269

270270
if _, err := setDeploymentContainerEnvs(existingDeployment, desiredDeployment.Spec.Template.Spec.Containers[0].Env, PostgresDeploymentName); err != nil {
271271
return err

internal/controller/ols_app_postgres_reconciliator.go

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ func (r *OLSConfigReconciler) reconcilePostgresServer(ctx context.Context, olsco
3030
Name: "reconcile Postgres Secret",
3131
Task: r.reconcilePostgresSecret,
3232
},
33+
{
34+
Name: "reconcile Postgres CA Secret",
35+
Task: r.reconcilePostgresCA,
36+
},
3337
{
3438
Name: "reconcile Postgres Service",
3539
Task: r.reconcilePostgresService,
@@ -70,14 +74,13 @@ func (r *OLSConfigReconciler) reconcilePostgresDeployment(ctx context.Context, c
7074
existingDeployment := &appsv1.Deployment{}
7175
err = r.Get(ctx, client.ObjectKey{Name: PostgresDeploymentName, Namespace: r.Options.Namespace}, existingDeployment)
7276
if err != nil && errors.IsNotFound(err) {
73-
updateDeploymentAnnotations(desiredDeployment, map[string]string{
74-
PostgresConfigHashKey: r.stateCache[PostgresConfigHashStateCacheKey],
75-
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
76-
})
77-
updateDeploymentTemplateAnnotations(desiredDeployment, map[string]string{
77+
annotations := map[string]string{
7878
PostgresConfigHashKey: r.stateCache[PostgresConfigHashStateCacheKey],
7979
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
80-
})
80+
PostgresCAHashKey: r.stateCache[PostgresCAHashStateCacheKey],
81+
}
82+
updateDeploymentAnnotations(desiredDeployment, annotations)
83+
updateDeploymentTemplateAnnotations(desiredDeployment, annotations)
8184
r.logger.Info("creating a new OLS postgres deployment", "deployment", desiredDeployment.Name)
8285
err = r.Create(ctx, desiredDeployment)
8386
if err != nil {
@@ -273,3 +276,61 @@ func (r *OLSConfigReconciler) reconcilePostgresNetworkPolicy(ctx context.Context
273276
r.logger.Info("OLS postgres network policy reconciled", "network policy", networkPolicy.Name)
274277
return nil
275278
}
279+
280+
func (r *OLSConfigReconciler) reconcilePostgresCA(ctx context.Context, cr *olsv1alpha1.OLSConfig) error {
281+
certBytes := []byte{}
282+
283+
// Get service CA certificate from ConfigMap
284+
tmpCM := &corev1.ConfigMap{}
285+
err := r.Client.Get(ctx, client.ObjectKey{Name: OLSCAConfigMap, Namespace: r.Options.Namespace}, tmpCM)
286+
if err != nil {
287+
if !errors.IsNotFound(err) {
288+
return fmt.Errorf("failed to get %s ConfigMap: %w", OLSCAConfigMap, err)
289+
}
290+
r.logger.Info("CA ConfigMap not found, skipping CA bundle", "configmap", OLSCAConfigMap)
291+
} else {
292+
if caCert, exists := tmpCM.Data[PostgresServiceCACertKeyName]; exists {
293+
certBytes = append(certBytes, []byte(PostgresServiceCACertKeyName)...)
294+
certBytes = append(certBytes, []byte(caCert)...)
295+
}
296+
}
297+
298+
// Get serving cert from Secret
299+
tmpSec := &corev1.Secret{}
300+
err = r.Client.Get(ctx, client.ObjectKey{Name: PostgresCertsSecretName, Namespace: r.Options.Namespace}, tmpSec)
301+
if err != nil {
302+
if !errors.IsNotFound(err) {
303+
return fmt.Errorf("failed to get %s Secret: %w", PostgresCertsSecretName, err)
304+
}
305+
r.logger.Info("serving cert Secret not found, skipping server certificate", "secret", PostgresCertsSecretName)
306+
} else {
307+
if tlsCert, exists := tmpSec.Data[PostgresTLSCertKeyName]; exists {
308+
certBytes = append(certBytes, []byte(PostgresTLSCertKeyName)...)
309+
certBytes = append(certBytes, tlsCert...)
310+
}
311+
}
312+
313+
// Calculate hash based on available inputs
314+
combinedHash := ""
315+
if len(certBytes) > 0 {
316+
var err error
317+
if combinedHash, err = hashBytes(certBytes); err != nil {
318+
return fmt.Errorf("failed to generate Postgres CA hash: %w", err)
319+
}
320+
}
321+
322+
// Store existing hash before updating
323+
existingHash := r.stateCache[PostgresCAHashStateCacheKey]
324+
325+
// Always update state cache to ensure it's set, even if value hasn't changed
326+
r.stateCache[PostgresCAHashStateCacheKey] = combinedHash
327+
328+
// Check if hash changed (including changes to/from empty string)
329+
if combinedHash == existingHash {
330+
return nil
331+
}
332+
333+
r.logger.Info("Postgres CA hash updated, deployment will be updated via updatePostgresDeployment")
334+
335+
return nil
336+
}

internal/controller/ols_app_server_deployment.go

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
appsv1 "k8s.io/api/apps/v1"
1010
corev1 "k8s.io/api/core/v1"
11-
apierrors "k8s.io/apimachinery/pkg/api/errors"
11+
"k8s.io/apimachinery/pkg/api/errors"
1212
"k8s.io/apimachinery/pkg/api/resource"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414
"k8s.io/apimachinery/pkg/util/intstr"
@@ -461,22 +461,39 @@ func (r *OLSConfigReconciler) updateOLSDeployment(ctx context.Context, existingD
461461
existingDeployment.Annotations[OLSConfigHashKey] != r.stateCache[OLSConfigHashStateCacheKey] ||
462462
existingDeployment.Annotations[OLSAppTLSHashKey] != r.stateCache[OLSAppTLSHashStateCacheKey] ||
463463
existingDeployment.Annotations[LLMProviderHashKey] != r.stateCache[LLMProviderHashStateCacheKey] ||
464-
existingDeployment.Annotations[PostgresSecretHashKey] != r.stateCache[PostgresSecretHashStateCacheKey] {
465-
updateDeploymentAnnotations(existingDeployment, map[string]string{
464+
existingDeployment.Annotations[PostgresSecretHashKey] != r.stateCache[PostgresSecretHashStateCacheKey] ||
465+
existingDeployment.Annotations[PostgresCAHashKey] != r.stateCache[PostgresCAHashStateCacheKey] {
466+
467+
// Check if PostgreSQL CA hash changed - if so, verify PostgreSQL deployment is ready before restarting app-server
468+
postgresCAHashChanged := existingDeployment.Annotations[PostgresCAHashKey] != r.stateCache[PostgresCAHashStateCacheKey]
469+
if postgresCAHashChanged {
470+
postgresDeployment := &appsv1.Deployment{}
471+
err := r.Get(ctx, client.ObjectKey{Name: PostgresDeploymentName, Namespace: r.Options.Namespace}, postgresDeployment)
472+
if err == nil {
473+
// PostgreSQL deployment exists, check if it's ready
474+
_, checkErr := r.checkDeploymentStatus(postgresDeployment)
475+
if checkErr != nil {
476+
// PostgreSQL is not ready yet, skip app-server update to avoid readiness failures
477+
r.logger.Info("PostgreSQL deployment is not ready yet, skipping app-server update to prevent readiness failures",
478+
"postgres_ready_replicas", postgresDeployment.Status.ReadyReplicas,
479+
"postgres_desired_replicas", *postgresDeployment.Spec.Replicas)
480+
return nil
481+
}
482+
r.logger.Info("PostgreSQL deployment is ready, proceeding with app-server update")
483+
}
484+
}
485+
486+
annotations := map[string]string{
466487
OLSConfigHashKey: r.stateCache[OLSConfigHashStateCacheKey],
467488
OLSAppTLSHashKey: r.stateCache[OLSAppTLSHashStateCacheKey],
468489
LLMProviderHashKey: r.stateCache[LLMProviderHashStateCacheKey],
469490
AdditionalCAHashKey: r.stateCache[AdditionalCAHashStateCacheKey],
470491
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
471-
})
492+
PostgresCAHashKey: r.stateCache[PostgresCAHashStateCacheKey],
493+
}
494+
updateDeploymentAnnotations(existingDeployment, annotations)
472495
// update the deployment template annotation triggers the rolling update
473-
updateDeploymentTemplateAnnotations(existingDeployment, map[string]string{
474-
OLSConfigHashKey: r.stateCache[OLSConfigHashStateCacheKey],
475-
OLSAppTLSHashKey: r.stateCache[OLSAppTLSHashStateCacheKey],
476-
LLMProviderHashKey: r.stateCache[LLMProviderHashStateCacheKey],
477-
AdditionalCAHashKey: r.stateCache[AdditionalCAHashStateCacheKey],
478-
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
479-
})
496+
updateDeploymentTemplateAnnotations(existingDeployment, annotations)
480497
changed = true
481498
}
482499

@@ -557,17 +574,18 @@ func (r *OLSConfigReconciler) telemetryEnabled() (bool, error) {
557574

558575
pullSecret := &corev1.Secret{}
559576
err := r.Get(context.Background(), client.ObjectKey{Namespace: pullSecretNamespace, Name: pullSecretName}, pullSecret)
560-
561577
if err != nil {
562-
if apierrors.IsNotFound(err) {
578+
if errors.IsNotFound(err) {
579+
// Secret doesn't exist - telemetry is not enabled (normal in test environments)
563580
return false, nil
564581
}
565582
return false, err
566583
}
567584

568585
dockerconfigjson, ok := pullSecret.Data[".dockerconfigjson"]
569586
if !ok {
570-
return false, fmt.Errorf("pull secret does not contain .dockerconfigjson")
587+
// Secret exists but doesn't have the expected key - telemetry not properly configured
588+
return false, nil
571589
}
572590

573591
dockerconfigjsonDecoded := map[string]interface{}{}

internal/controller/ols_app_server_reconciliator.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ func (r *OLSConfigReconciler) reconcileOLSConfigMap(ctx context.Context, cr *ols
140140

141141
func (r *OLSConfigReconciler) reconcileOLSAdditionalCAConfigMap(ctx context.Context, cr *olsv1alpha1.OLSConfig) error {
142142
if cr.Spec.OLSConfig.AdditionalCAConfigMapRef == nil {
143-
// no additional CA certs, skip
144-
r.logger.Info("Additional CA not configured, reconciliation skipped")
143+
// no additional CA certs, set empty hash
144+
r.logger.Info("Additional CA not configured, setting empty hash")
145+
r.stateCache[AdditionalCAHashStateCacheKey] = ""
145146
return nil
146147
}
147148

@@ -278,18 +279,15 @@ func (r *OLSConfigReconciler) reconcileDeployment(ctx context.Context, cr *olsv1
278279
existingDeployment := &appsv1.Deployment{}
279280
err = r.Get(ctx, client.ObjectKey{Name: OLSAppServerDeploymentName, Namespace: r.Options.Namespace}, existingDeployment)
280281
if err != nil && errors.IsNotFound(err) {
281-
updateDeploymentAnnotations(desiredDeployment, map[string]string{
282+
annotations := map[string]string{
282283
OLSConfigHashKey: r.stateCache[OLSConfigHashStateCacheKey],
283284
OLSAppTLSHashKey: r.stateCache[OLSAppTLSHashStateCacheKey],
284285
LLMProviderHashKey: r.stateCache[LLMProviderHashStateCacheKey],
285286
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
286-
})
287-
updateDeploymentTemplateAnnotations(desiredDeployment, map[string]string{
288-
OLSConfigHashKey: r.stateCache[OLSConfigHashStateCacheKey],
289-
OLSAppTLSHashKey: r.stateCache[OLSAppTLSHashStateCacheKey],
290-
LLMProviderHashKey: r.stateCache[LLMProviderHashStateCacheKey],
291-
PostgresSecretHashKey: r.stateCache[PostgresSecretHashStateCacheKey],
292-
})
287+
PostgresCAHashKey: r.stateCache[PostgresCAHashStateCacheKey],
288+
}
289+
updateDeploymentAnnotations(desiredDeployment, annotations)
290+
updateDeploymentTemplateAnnotations(desiredDeployment, annotations)
293291
r.logger.Info("creating a new deployment", "deployment", desiredDeployment.Name)
294292
err = r.Create(ctx, desiredDeployment)
295293
if err != nil {

internal/controller/olsconfig_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,15 @@ func (r *OLSConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
351351
Owns(&corev1.PersistentVolumeClaim{}).
352352
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(secretWatcherFilter)).
353353
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(telemetryPullSecretWatcherFilter)).
354+
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
355+
return r.postgresCAWatcherFilter(ctx, obj)
356+
})).
354357
Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
355358
return r.configMapWatcherFilter(ctx, obj)
356359
})).
360+
Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
361+
return r.postgresCAWatcherFilter(ctx, obj)
362+
})).
357363
Owns(&consolev1.ConsolePlugin{}).
358364
Owns(&monv1.ServiceMonitor{}).
359365
Owns(&monv1.PrometheusRule{}).

internal/controller/resource_watchers.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,29 @@ func (r *OLSConfigReconciler) restartAppServer(ctx context.Context, inCluster bo
124124
}
125125
return nil
126126
}
127+
128+
// postgresCAWatcherFilter watches for changes to PostgreSQL CA certificate resources
129+
func (r *OLSConfigReconciler) postgresCAWatcherFilter(ctx context.Context, obj client.Object) []reconcile.Request {
130+
// Only watch resources in the operator's namespace
131+
if obj.GetNamespace() != r.Options.Namespace {
132+
return nil
133+
}
134+
135+
// Watch the openshift-service-ca.crt ConfigMap
136+
if obj.GetName() == OLSCAConfigMap {
137+
return []reconcile.Request{
138+
{NamespacedName: types.NamespacedName{
139+
Name: OLSConfigName,
140+
}},
141+
}
142+
}
143+
// Watch the PostgreSQL serving certificate Secret
144+
if obj.GetName() == PostgresCertsSecretName {
145+
return []reconcile.Request{
146+
{NamespacedName: types.NamespacedName{
147+
Name: OLSConfigName,
148+
}},
149+
}
150+
}
151+
return nil
152+
}

0 commit comments

Comments
 (0)