Add NFS workaround to GerritCluster
Change-Id: I48641f0ac0fa714338ec1fb4e5e45ce1555d73e6
diff --git a/operator/README.md b/operator/README.md
index ad90b27..eff137a 100644
--- a/operator/README.md
+++ b/operator/README.md
@@ -79,6 +79,14 @@
## to be used in all containers
imagePullPolicy: "Always"
+ ## The busybox container is used for some init containers.
+ busyBox:
+ ## The registry from which to pull the "busybox' image
+ registry: docker.io
+
+ ## The tag/version of the 'busybox' image
+ tag: latest
+
storageClasses:
## Name of a StorageClass allowing ReadWriteOnce access. (default: default)
readWriteOnce: default
@@ -86,6 +94,26 @@
## Name of a StorageClass allowing ReadWriteMany access. (default: shared-storage)
readWriteMany: nfs-client
+ ## NFS is not well supported by Kubernetes. These options provide a workaround
+ ## to ensure correct file ownership and id mapping
+ nfsWorkaround:
+ ## If enabled, file ownership will be manually set, if a volume is mounted
+ ## for the first time.
+ enabled: false
+
+ ## The idmapd.config file can be used to e.g. configure the ID domain. This
+ ## might be necessary for some NFS servers to ensure correct mapping of
+ ## user and group IDs.
+ idmapdConfig: |-
+ [General]
+ Verbosity = 0
+ Domain = localdomain.com
+
+ [Mapping]
+ Nobody-User = nobody
+ Nobody-Group = nogroup
+
+
## Storage for git repositories
gitRepositoryStorage:
## Size of the volume (ReadWriteMany) used to store git repositories. (mandatory)
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java
index eb2ee7d..ecbb32d 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java
@@ -36,7 +36,7 @@
KubernetesClient client = new DefaultKubernetesClient(config);
Operator operator = new Operator(client);
logger.atFine().log("Registering GerritCluster Reconciler");
- operator.register(new GerritClusterReconciler());
+ operator.register(new GerritClusterReconciler(client));
logger.atFine().log("Registering GitGc Reconciler");
operator.register(new GitGarbageCollectionReconciler(client));
operator.installShutdownHook();
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/BusyBoxImage.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/BusyBoxImage.java
new file mode 100644
index 0000000..b5e8af4
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/BusyBoxImage.java
@@ -0,0 +1,62 @@
+// 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.cluster;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+public class BusyBoxImage {
+ private String registry;
+ private String tag;
+
+ public BusyBoxImage() {
+ this.registry = "docker.io";
+ this.tag = "latest";
+ }
+
+ public void setRegistry(String registry) {
+ this.registry = registry;
+ }
+
+ public String getRegistry() {
+ return registry;
+ }
+
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ @JsonIgnore
+ public String getBusyBoxImage() {
+ StringBuilder builder = new StringBuilder();
+
+ if (this.registry != null) {
+ builder.append(registry);
+ builder.append("/");
+ }
+
+ builder.append("busybox");
+
+ if (this.tag != null) {
+ builder.append(":");
+ builder.append(tag);
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java
index d2a8297..196a818 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.k8s.operator.cluster.GerritLogsPVC.LOGS_PVC_NAME;
import static com.google.gerrit.k8s.operator.cluster.GitRepositoriesPVC.REPOSITORY_PVC_NAME;
+import static com.google.gerrit.k8s.operator.cluster.NfsIdmapdConfigMap.NFS_IDMAPD_CM_NAME;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.fabric8.kubernetes.api.model.Namespaced;
@@ -40,6 +41,7 @@
private static final long serialVersionUID = 1L;
private static final String GIT_REPOSITORIES_VOLUME_NAME = "git-repositories";
private static final String LOGS_VOLUME_NAME = "logs";
+ private static final String NFS_IDMAPD_CONFIG_VOLUME_NAME = "nfs-config";
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
@@ -95,4 +97,23 @@
.withMountPath("/var/gerrit/logs")
.build();
}
+
+ @JsonIgnore
+ public Volume getNfsImapdConfigVolume() {
+ return new VolumeBuilder()
+ .withName(NFS_IDMAPD_CONFIG_VOLUME_NAME)
+ .withNewConfigMap()
+ .withName(NFS_IDMAPD_CM_NAME)
+ .endConfigMap()
+ .build();
+ }
+
+ @JsonIgnore
+ public VolumeMount getNfsImapdConfigVolumeMount() {
+ return new VolumeMountBuilder()
+ .withName(NFS_IDMAPD_CONFIG_VOLUME_NAME)
+ .withMountPath("/etc/idmapd.conf")
+ .withSubPath("idmapd.conf")
+ .build();
+ }
}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
index 29d4d73..ea5c2e4 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
@@ -14,21 +14,50 @@
package com.google.gerrit.k8s.operator.cluster;
+import io.fabric8.kubernetes.client.KubernetesClient;
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.source.EventSource;
+import java.util.Map;
@ControllerConfiguration(
dependents = {
@Dependent(type = GitRepositoriesPVC.class),
@Dependent(type = GerritLogsPVC.class),
})
-public class GerritClusterReconciler implements Reconciler<GerritCluster> {
+public class GerritClusterReconciler
+ implements Reconciler<GerritCluster>, EventSourceInitializer<GerritCluster> {
+ private final KubernetesClient kubernetesClient;
+
+ private NfsIdmapdConfigMap dependentNfsImapdConfigMap;
+
+ public GerritClusterReconciler(KubernetesClient client) {
+ this.kubernetesClient = client;
+
+ this.dependentNfsImapdConfigMap = new NfsIdmapdConfigMap();
+ this.dependentNfsImapdConfigMap.setKubernetesClient(kubernetesClient);
+ }
+
+ @Override
+ public Map<String, EventSource> prepareEventSources(EventSourceContext<GerritCluster> context) {
+ return EventSourceInitializer.nameEventSources(
+ this.dependentNfsImapdConfigMap.initEventSource(context));
+ }
+
@Override
public UpdateControl<GerritCluster> reconcile(
GerritCluster gerritCluster, Context<GerritCluster> context) {
+ if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().isEnabled()
+ && gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
+ != null) {
+ dependentNfsImapdConfigMap.reconcile(gerritCluster, context);
+ }
+
return UpdateControl.noUpdate();
}
}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java
index ea15b3b..2d0c538 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java
@@ -25,6 +25,7 @@
private SharedStorage logsStorage;
private String imagePullPolicy;
private Set<LocalObjectReference> imagePullSecrets = new HashSet<>();
+ private BusyBoxImage busyBox = new BusyBoxImage();
public StorageClassConfig getStorageClasses() {
return storageClasses;
@@ -65,4 +66,12 @@
public void setImagePullSecrets(Set<LocalObjectReference> imagePullSecrets) {
this.imagePullSecrets = imagePullSecrets;
}
+
+ public BusyBoxImage getBusyBox() {
+ return busyBox;
+ }
+
+ public void setBusyBox(BusyBoxImage busyBox) {
+ this.busyBox = busyBox;
+ }
}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/NfsIdmapdConfigMap.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/NfsIdmapdConfigMap.java
new file mode 100644
index 0000000..4f07fbf
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/NfsIdmapdConfigMap.java
@@ -0,0 +1,46 @@
+// 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.cluster;
+
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
+import java.util.Map;
+
+@KubernetesDependent(labelSelector = "app.kubernetes.io/component=git-repositories-storage")
+public class NfsIdmapdConfigMap extends CRUDKubernetesDependentResource<ConfigMap, GerritCluster> {
+ public static final String NFS_IDMAPD_CM_NAME = "nfs-idmapd-config";
+
+ public NfsIdmapdConfigMap() {
+ super(ConfigMap.class);
+ }
+
+ @Override
+ protected ConfigMap desired(GerritCluster gerritCluster, Context<GerritCluster> context) {
+ return new ConfigMapBuilder()
+ .withNewMetadata()
+ .withName(NFS_IDMAPD_CM_NAME)
+ .withNamespace(gerritCluster.getMetadata().getNamespace())
+ .withLabels(gerritCluster.getLabels(NFS_IDMAPD_CM_NAME, this.getClass().getSimpleName()))
+ .endMetadata()
+ .withData(
+ Map.of(
+ "idmapd.conf",
+ gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig()))
+ .build();
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/NfsWorkaroundConfig.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/NfsWorkaroundConfig.java
new file mode 100644
index 0000000..9d1949d
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/NfsWorkaroundConfig.java
@@ -0,0 +1,37 @@
+// 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.cluster;
+
+public class NfsWorkaroundConfig {
+
+ private boolean enabled;
+ private String idmapdConfig;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public String getIdmapdConfig() {
+ return idmapdConfig;
+ }
+
+ public void setIdmapdConfig(String idmapdConfig) {
+ this.idmapdConfig = idmapdConfig;
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/StorageClassConfig.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/StorageClassConfig.java
index 9642172..d7133d4 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/StorageClassConfig.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/StorageClassConfig.java
@@ -18,6 +18,7 @@
String readWriteOnce = "default";
String readWriteMany = "shared-storage";
+ NfsWorkaroundConfig nfsWorkaround = new NfsWorkaroundConfig();
public String getReadWriteOnce() {
return readWriteOnce;
@@ -34,4 +35,12 @@
public void setReadWriteMany(String readWriteMany) {
this.readWriteMany = readWriteMany;
}
+
+ public NfsWorkaroundConfig getNfsWorkaround() {
+ return nfsWorkaround;
+ }
+
+ public void setNfsWorkaround(NfsWorkaroundConfig nfsWorkaround) {
+ this.nfsWorkaround = nfsWorkaround;
+ }
}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java
index 3ba47e8..2cc2d2f 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java
@@ -20,6 +20,8 @@
import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
+import io.fabric8.kubernetes.api.model.Volume;
+import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.batch.v1.CronJob;
import io.fabric8.kubernetes.api.model.batch.v1.CronJobBuilder;
import io.fabric8.kubernetes.api.model.batch.v1.JobTemplateSpec;
@@ -58,6 +60,18 @@
Map<String, String> gitGcLabels =
gerritCluster.getLabels("GitGc", this.getClass().getSimpleName());
+ List<Container> initContainers = new ArrayList<>();
+ List<Volume> volumes =
+ List.of(gerritCluster.getGitRepositoriesVolume(), gerritCluster.getLogsVolume());
+
+ if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().isEnabled()) {
+ initContainers.add(createNfsImapdInitContainer(gerritCluster));
+ if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
+ != null) {
+ volumes.add(gerritCluster.getNfsImapdConfigVolume());
+ }
+ }
+
JobTemplateSpec gitGcJobTemplate =
new JobTemplateSpecBuilder()
.withNewSpec()
@@ -80,8 +94,7 @@
.withFsGroup(100L)
.endSecurityContext()
.addToContainers(buildGitGcContainer(gitGc, gerritCluster))
- .withVolumes(
- List.of(gerritCluster.getGitRepositoriesVolume(), gerritCluster.getLogsVolume()))
+ .withVolumes(volumes)
.endSpec()
.endTemplate()
.endSpec()
@@ -110,16 +123,46 @@
.build();
}
+ private Container createNfsImapdInitContainer(GerritCluster gerritCluster) {
+ List<VolumeMount> volumeMounts = List.of(gerritCluster.getLogsVolumeMount());
+
+ if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().isEnabled()
+ && gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
+ != null) {
+ volumeMounts.add(gerritCluster.getNfsImapdConfigVolumeMount());
+ }
+
+ return new ContainerBuilder()
+ .withName("nfs-init")
+ .withImagePullPolicy(gerritCluster.getSpec().getImagePullPolicy())
+ .withImage(gerritCluster.getSpec().getBusyBox().getBusyBoxImage())
+ .withCommand(List.of("sh", "-c"))
+ .withArgs("chown -R 1000:100 /var/mnt/logs")
+ .withEnv(getPodNameEnvVar())
+ .withVolumeMounts(volumeMounts)
+ .build();
+ }
+
+ private static EnvVar getPodNameEnvVar() {
+ return new EnvVarBuilder()
+ .withName("POD_NAME")
+ .withNewValueFrom()
+ .withNewFieldRef()
+ .withFieldPath("metadata.name")
+ .endFieldRef()
+ .endValueFrom()
+ .build();
+ }
+
private Container buildGitGcContainer(GitGarbageCollection gitGc, GerritCluster gerritCluster) {
- EnvVar podNameEnvVar =
- new EnvVarBuilder()
- .withName("POD_NAME")
- .withNewValueFrom()
- .withNewFieldRef()
- .withFieldPath("metadata.name")
- .endFieldRef()
- .endValueFrom()
- .build();
+ List<VolumeMount> volumeMounts =
+ List.of(gerritCluster.getGitRepositoriesVolumeMount(), gerritCluster.getLogsVolumeMount());
+
+ if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().isEnabled()
+ && gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
+ != null) {
+ volumeMounts.add(gerritCluster.getNfsImapdConfigVolumeMount());
+ }
ContainerBuilder gitGcContainerBuilder =
new ContainerBuilder()
@@ -127,11 +170,8 @@
.withImagePullPolicy(gerritCluster.getSpec().getImagePullPolicy())
.withImage(gitGc.getSpec().getImage())
.withResources(gitGc.getSpec().getResources())
- .withEnv(podNameEnvVar)
- .withVolumeMounts(
- List.of(
- gerritCluster.getGitRepositoriesVolumeMount(),
- gerritCluster.getLogsVolumeMount()));
+ .withEnv(getPodNameEnvVar())
+ .withVolumeMounts(volumeMounts);
ArrayList<String> args = new ArrayList<>();
for (String project : gitGc.getSpec().getProjects()) {
diff --git a/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java b/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java
index 436bcf6..272dfb6 100644
--- a/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java
+++ b/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java
@@ -22,6 +22,7 @@
import static org.hamcrest.Matchers.notNullValue;
import com.google.common.flogger.FluentLogger;
+import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.api.model.Quantity;
@@ -38,12 +39,12 @@
LocallyRunOperatorExtension operator =
LocallyRunOperatorExtension.builder()
.waitForNamespaceDeletion(true)
- .withReconciler(new GerritClusterReconciler())
+ .withReconciler(new GerritClusterReconciler(client))
.build();
@Test
void testGitRepositoriesPvcCreated() {
- GerritCluster cluster = createGerritCluster();
+ GerritCluster cluster = createGerritCluster(false);
logger.atInfo().log("Waiting max 1 minutes for the git repositories pvc to be created.");
await()
@@ -65,7 +66,7 @@
@Test
void testGerritLogsPvcCreated() {
- GerritCluster cluster = createGerritCluster();
+ GerritCluster cluster = createGerritCluster(false);
logger.atInfo().log("Waiting max 1 minutes for the gerrit logs pvc to be created.");
await()
@@ -85,7 +86,28 @@
client.resource(cluster).delete();
}
- private GerritCluster createGerritCluster() {
+ @Test
+ void testNfsIdmapdConfigMapCreated() {
+ GerritCluster cluster = createGerritCluster(true);
+
+ logger.atInfo().log("Waiting max 1 minutes for the nfs idmapd configmap to be created.");
+ await()
+ .atMost(1, MINUTES)
+ .untilAsserted(
+ () -> {
+ ConfigMap cm =
+ client
+ .configMaps()
+ .inNamespace(operator.getNamespace())
+ .withName(NfsIdmapdConfigMap.NFS_IDMAPD_CM_NAME)
+ .get();
+ assertThat(cm, is(notNullValue()));
+ });
+
+ logger.atInfo().log("Deleting test cluster object: %s", cluster);
+ }
+
+ private GerritCluster createGerritCluster(boolean isNfsEnbaled) {
GerritCluster cluster = new GerritCluster();
cluster.setMetadata(
@@ -103,6 +125,11 @@
StorageClassConfig storageClassConfig = new StorageClassConfig();
storageClassConfig.setReadWriteMany(System.getProperty("rwmStorageClass", "nfs-client"));
+ NfsWorkaroundConfig nfsWorkaround = new NfsWorkaroundConfig();
+ nfsWorkaround.setEnabled(isNfsEnbaled);
+ nfsWorkaround.setIdmapdConfig("[General]\nDomain = localdomain.com");
+ storageClassConfig.setNfsWorkaround(nfsWorkaround);
+
GerritClusterSpec clusterSpec = new GerritClusterSpec();
clusterSpec.setGitRepositoryStorage(repoStorage);
clusterSpec.setLogsStorage(logStorage);
diff --git a/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java b/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java
index d4c51b0..d8b0c5e 100644
--- a/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java
+++ b/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java
@@ -54,7 +54,7 @@
LocallyRunOperatorExtension.builder()
.waitForNamespaceDeletion(true)
.withReconciler(new GitGarbageCollectionReconciler(client))
- .withReconciler(new GerritClusterReconciler())
+ .withReconciler(new GerritClusterReconciler(client))
.build();
@Test