Repository navigation
Fix secret reconciliation updates for the listener pod #4492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,8 +17,10 @@ limitations under the License. | |
| package actionsgithubcom | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "fmt" | ||
| "maps" | ||
| "time" | ||
|
|
||
| "github.com/go-logr/logr" | ||
|
|
@@ -210,6 +212,30 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | |
|
|
||
| // TODO: make sure the role binding has the up-to-date role and service account | ||
|
|
||
| // Reconcile listener config secret and detect drift | ||
| cert := "" | ||
| if autoscalingListener.Spec.GitHubServerTLS != nil { | ||
| var err error | ||
| cert, err = r.certificate(ctx, &autoscalingRunnerSet, autoscalingListener) | ||
| if err != nil { | ||
| return ctrl.Result{}, fmt.Errorf("failed to build GitHub server TLS certificate value for listener config: %w", err) | ||
| } | ||
| } | ||
|
|
||
| var metricsConfig *listenerMetricsServerConfig | ||
| if r.ListenerMetricsAddr != "0" { | ||
| metricsConfig = &listenerMetricsServerConfig{ | ||
| addr: r.ListenerMetricsAddr, | ||
| endpoint: r.ListenerMetricsEndpoint, | ||
| } | ||
| } | ||
|
|
||
| secretChanged, err := r.reconcileListenerConfigSecret(ctx, autoscalingListener, appConfig, metricsConfig, cert, log) | ||
| if err != nil { | ||
| log.Error(err, "Failed to reconcile listener config secret") | ||
| return ctrl.Result{}, err | ||
| } | ||
|
|
||
| listenerPod := new(corev1.Pod) | ||
| if err := r.Get( | ||
| ctx, | ||
|
|
@@ -234,6 +260,12 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl. | |
| return r.createListenerPod(ctx, &autoscalingRunnerSet, autoscalingListener, serviceAccount, appConfig, log) | ||
| } | ||
|
|
||
| // If listener config secret changed and pod exists, restart the pod | ||
| if secretChanged { | ||
| log.Info("Listener config secret changed, restarting listener pod") | ||
| return ctrl.Result{}, r.deleteListenerPod(ctx, autoscalingListener, listenerPod, log) | ||
| } | ||
|
|
||
| cs := listenerContainerStatus(listenerPod) | ||
| switch { | ||
| case listenerPod.Status.Reason == "Evicted": | ||
|
|
@@ -285,19 +317,6 @@ func (r *AutoscalingListenerReconciler) deleteListenerPod(ctx context.Context, a | |
| log.Error(err, "Unable to delete the listener pod", "namespace", listenerPod.Namespace, "name", listenerPod.Name) | ||
| return err | ||
| } | ||
|
|
||
| // delete the listener config secret as well, so it gets recreated when the listener pod is recreated, with any new data if it exists | ||
| var configSecret corev1.Secret | ||
| err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerConfigName(autoscalingListener)}, &configSecret) | ||
| switch { | ||
| case err == nil && configSecret.DeletionTimestamp.IsZero(): | ||
| log.Info("Deleting the listener config secret") | ||
| if err := r.Delete(ctx, &configSecret); err != nil { | ||
| return fmt.Errorf("failed to delete listener config secret: %w", err) | ||
| } | ||
| case !kerrors.IsNotFound(err): | ||
| return fmt.Errorf("failed to get the listener config secret: %w", err) | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
@@ -425,6 +444,47 @@ func (r *AutoscalingListenerReconciler) createServiceAccountForListener(ctx cont | |
| return ctrl.Result{}, nil | ||
| } | ||
|
|
||
| func (r *AutoscalingListenerReconciler) reconcileListenerConfigSecret(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, appConfig *appconfig.AppConfig, metricsConfig *listenerMetricsServerConfig, cert string, logger logr.Logger) (changed bool, err error) { | ||
| desiredSecret, err := r.newScaleSetListenerConfig(autoscalingListener, appConfig, metricsConfig, cert) | ||
| if err != nil { | ||
| return false, fmt.Errorf("failed to build listener config secret: %w", err) | ||
| } | ||
|
|
||
| existingSecret := &corev1.Secret{} | ||
| err = r.Get(ctx, types.NamespacedName{Name: desiredSecret.Name, Namespace: desiredSecret.Namespace}, existingSecret) | ||
| if err != nil { | ||
| if kerrors.IsNotFound(err) { | ||
| if err := ctrl.SetControllerReference(autoscalingListener, desiredSecret, r.Scheme); err != nil { | ||
| return false, fmt.Errorf("failed to set controller reference: %w", err) | ||
| } | ||
|
|
||
| logger.Info("Creating listener config secret", "namespace", desiredSecret.Namespace, "name", desiredSecret.Name) | ||
| if err := r.Create(ctx, desiredSecret); err != nil { | ||
| return false, fmt.Errorf("failed to create listener config secret: %w", err) | ||
| } | ||
|
|
||
| return true, nil | ||
| } | ||
| return false, fmt.Errorf("failed to get listener config secret: %w", err) | ||
| } | ||
|
|
||
| if listenerConfigSecretDrifted(existingSecret, desiredSecret) { | ||
| updatedSecret := existingSecret.DeepCopy() | ||
| updatedSecret.Data = desiredSecret.Data | ||
| updatedSecret.Labels = desiredSecret.Labels | ||
| updatedSecret.Annotations = desiredSecret.Annotations | ||
|
|
||
| logger.Info("Updating listener config secret", "namespace", updatedSecret.Namespace, "name", updatedSecret.Name) | ||
| if err := r.Update(ctx, updatedSecret); err != nil { | ||
| return false, fmt.Errorf("failed to update listener config secret: %w", err) | ||
| } | ||
|
|
||
| return true, nil | ||
| } | ||
|
|
||
| return false, nil | ||
| } | ||
|
|
||
| func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, autoscalingListener *v1alpha1.AutoscalingListener, serviceAccount *corev1.ServiceAccount, appConfig *appconfig.AppConfig, logger logr.Logger) (ctrl.Result, error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (bit out of scope of this PR but) I just found createListenerPod has some dead code (e.g., create cert, autoscalingRunnerSet/appConfig etc...) I can create follow up refactor PR after merging this PR
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to do it! |
||
| var envs []corev1.EnvVar | ||
| if autoscalingListener.Spec.Proxy != nil { | ||
|
|
@@ -691,6 +751,27 @@ func (r *AutoscalingListenerReconciler) publishRunningListener(autoscalingListen | |
| return nil | ||
| } | ||
|
|
||
| // listenerConfigSecretDrifted detects if the listener config secret has drifted from the desired state. | ||
| // It compares the config.json data, labels, and annotations deterministically. | ||
| func listenerConfigSecretDrifted(existing *corev1.Secret, desired *corev1.Secret) bool { | ||
| // Compare config.json data | ||
| if !bytes.Equal(existing.Data["config.json"], desired.Data["config.json"]) { | ||
| return true | ||
| } | ||
|
|
||
| // Compare labels | ||
| if !maps.Equal(existing.Labels, desired.Labels) { | ||
| return true | ||
| } | ||
|
|
||
| // Compare annotations | ||
| if !maps.Equal(existing.Annotations, desired.Annotations) { | ||
| return true | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
| // SetupWithManager sets up the controller with the Manager. | ||
| func (r *AutoscalingListenerReconciler) SetupWithManager(mgr ctrl.Manager, opts ...Option) error { | ||
| labelBasedWatchFunc := func(_ context.Context, obj client.Object) []reconcile.Request { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be useful to detect failure 👀
(Looks calling two separate API calls in the same reconcile, so might occur race condition or something like this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should cause any issues. We updated the secret and we are removing the pod. These are two separate things, and the pod is about to be removed. Next time, we check the secret and only then create the pod. I think this should be safe, and I did it so we can react quicker. The ephemeral runner also sometime calls multiple API calls since reconciling on a single call was way slower.