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