| // Copyright (C) 2022 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.k8s.operator.receiver; |
| |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.k8s.operator.cluster.GerritCluster; |
| import io.fabric8.kubernetes.api.model.Secret; |
| import io.fabric8.kubernetes.client.KubernetesClient; |
| import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; |
| import io.javaoperatorsdk.operator.api.reconciler.Context; |
| import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; |
| import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; |
| import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; |
| import io.javaoperatorsdk.operator.api.reconciler.Reconciler; |
| import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; |
| import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; |
| import io.javaoperatorsdk.operator.processing.event.ResourceID; |
| import io.javaoperatorsdk.operator.processing.event.source.EventSource; |
| import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; |
| import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| @ControllerConfiguration( |
| dependents = { |
| @Dependent(name = "receiver-deployment", type = ReceiverDeploymentDependentResource.class), |
| @Dependent( |
| name = "receiver-service", |
| type = ReceiverServiceDependentResource.class, |
| dependsOn = {"receiver-deployment"}) |
| }) |
| public class ReceiverReconciler implements Reconciler<Receiver>, EventSourceInitializer<Receiver> { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| private static final String SECRET_EVENT_SOURCE_NAME = "secret-event-source"; |
| private final KubernetesClient client; |
| |
| public ReceiverReconciler(KubernetesClient client) { |
| this.client = client; |
| } |
| |
| @Override |
| public Map<String, EventSource> prepareEventSources(EventSourceContext<Receiver> context) { |
| final SecondaryToPrimaryMapper<GerritCluster> gerritClusterMapper = |
| (GerritCluster cluster) -> |
| context |
| .getPrimaryCache() |
| .list( |
| receiver -> |
| receiver.getSpec().getCluster().equals(cluster.getMetadata().getName())) |
| .map(ResourceID::fromResource) |
| .collect(Collectors.toSet()); |
| |
| InformerEventSource<GerritCluster, Receiver> gerritClusterEventSource = |
| new InformerEventSource<>( |
| InformerConfiguration.from(GerritCluster.class, context) |
| .withSecondaryToPrimaryMapper(gerritClusterMapper) |
| .build(), |
| context); |
| |
| final SecondaryToPrimaryMapper<Secret> secretMapper = |
| (Secret secret) -> |
| context |
| .getPrimaryCache() |
| .list( |
| receiver -> |
| receiver |
| .getSpec() |
| .getCredentialSecretRef() |
| .equals(secret.getMetadata().getName())) |
| .map(ResourceID::fromResource) |
| .collect(Collectors.toSet()); |
| |
| InformerEventSource<Secret, Receiver> secretEventSource = |
| new InformerEventSource<>( |
| InformerConfiguration.from(Secret.class, context) |
| .withSecondaryToPrimaryMapper(secretMapper) |
| .build(), |
| context); |
| |
| Map<String, EventSource> eventSources = |
| EventSourceInitializer.nameEventSources(gerritClusterEventSource); |
| eventSources.put(SECRET_EVENT_SOURCE_NAME, secretEventSource); |
| return eventSources; |
| } |
| |
| @Override |
| public UpdateControl<Receiver> reconcile(Receiver receiver, Context<Receiver> context) |
| throws Exception { |
| if (receiver.getStatus() != null && isReceiverRestartRequired(receiver, context)) { |
| restartReceiverDeployment(receiver); |
| } |
| |
| return UpdateControl.patchStatus(updateStatus(receiver, context)); |
| } |
| |
| void restartReceiverDeployment(Receiver receiver) { |
| logger.atInfo().log( |
| "Restarting Receiver %s due to configuration change.", receiver.getMetadata().getName()); |
| client |
| .apps() |
| .deployments() |
| .inNamespace(receiver.getMetadata().getNamespace()) |
| .withName(receiver.getMetadata().getName()) |
| .rolling() |
| .restart(); |
| } |
| |
| private Receiver updateStatus(Receiver receiver, Context<Receiver> context) { |
| ReceiverStatus status = receiver.getStatus(); |
| if (status == null) { |
| status = new ReceiverStatus(); |
| } |
| |
| Secret sec = |
| client |
| .secrets() |
| .inNamespace(receiver.getMetadata().getNamespace()) |
| .withName(receiver.getSpec().getCredentialSecretRef()) |
| .get(); |
| |
| if (sec != null) { |
| status.setAppliedCredentialSecretVersion(sec.getMetadata().getResourceVersion()); |
| } |
| |
| receiver.setStatus(status); |
| return receiver; |
| } |
| |
| private boolean isReceiverRestartRequired(Receiver receiver, Context<Receiver> context) { |
| String secVersion = |
| client |
| .secrets() |
| .inNamespace(receiver.getMetadata().getNamespace()) |
| .withName(receiver.getSpec().getCredentialSecretRef()) |
| .get() |
| .getMetadata() |
| .getResourceVersion(); |
| String appliedSecVersion = receiver.getStatus().getAppliedCredentialSecretVersion(); |
| if (!secVersion.equals(appliedSecVersion)) { |
| logger.atFine().log( |
| "Looking up Secret: %s; Installed secret resource version: %s; Resource version known to the Receiver: %s", |
| receiver.getSpec().getCredentialSecretRef(), secVersion, appliedSecVersion); |
| return true; |
| } |
| return false; |
| } |
| } |