[Operator] Mount shared directory, if HA-plugin is installed

The high-availability plugin uses a directory shared between all sites
to share websessions.

This change adds support to the operator to mount such a directory,
if

- the Gerrit is a primary Gerrit
- the Gerrit asks for at least 2 replicas
- the high-availability plugin is configured to be installed
  (it will not be automatically installed)

Change-Id: I44e08d2259f49c593910a80f65f3adb665711f5e
diff --git a/Documentation/operator-api-reference.md b/Documentation/operator-api-reference.md
index 5f688bd..4697adb 100644
--- a/Documentation/operator-api-reference.md
+++ b/Documentation/operator-api-reference.md
@@ -10,38 +10,39 @@
    6. [GerritNetwork](#gerritnetwork)
    7. [GerritClusterSpec](#gerritclusterspec)
    8. [GerritClusterStatus](#gerritclusterstatus)
-   9. [GerritStorageConfig](#gerritstorageconfig)
-   10. [StorageClassConfig](#storageclassconfig)
-   11. [NfsWorkaroundConfig](#nfsworkaroundconfig)
-   12. [SharedStorage](#sharedstorage)
-   13. [OptionalSharedStorage](#optionalsharedstorage)
-   14. [ContainerImageConfig](#containerimageconfig)
-   15. [BusyBoxImage](#busyboximage)
-   16. [GerritRepositoryConfig](#gerritrepositoryconfig)
-   17. [GerritClusterIngressConfig](#gerritclusteringressconfig)
-   18. [GerritIngressTlsConfig](#gerritingresstlsconfig)
-   19. [GerritTemplate](#gerrittemplate)
-   20. [GerritTemplateSpec](#gerrittemplatespec)
-   21. [GerritProbe](#gerritprobe)
-   22. [GerritServiceConfig](#gerritserviceconfig)
-   23. [GerritSite](#gerritsite)
-   24. [GerritPlugin](#gerritplugin)
-   25. [GerritMode](#gerritmode)
-   26. [GerritSpec](#gerritspec)
-   27. [GerritStatus](#gerritstatus)
-   28. [IngressConfig](#ingressconfig)
-   29. [ReceiverTemplate](#receivertemplate)
-   30. [ReceiverTemplateSpec](#receivertemplatespec)
-   31. [ReceiverSpec](#receiverspec)
-   32. [ReceiverStatus](#receiverstatus)
-   33. [ReceiverProbe](#receiverprobe)
-   34. [ReceiverServiceConfig](#receiverserviceconfig)
-   35. [GitGarbageCollectionSpec](#gitgarbagecollectionspec)
-   36. [GitGarbageCollectionStatus](#gitgarbagecollectionstatus)
-   37. [GitGcState](#gitgcstate)
-   38. [GerritNetworkSpec](#gerritnetworkspec)
-   39. [NetworkMember](#networkmember)
-   40. [NetworkMemberWithSsh](#networkmemberwithssh)
+   9. [StorageConfig](#storageconfig)
+   10. [GerritStorageConfig](#gerritstorageconfig)
+   11. [StorageClassConfig](#storageclassconfig)
+   12. [NfsWorkaroundConfig](#nfsworkaroundconfig)
+   13. [SharedStorage](#sharedstorage)
+   14. [OptionalSharedStorage](#optionalsharedstorage)
+   15. [ContainerImageConfig](#containerimageconfig)
+   16. [BusyBoxImage](#busyboximage)
+   17. [GerritRepositoryConfig](#gerritrepositoryconfig)
+   18. [GerritClusterIngressConfig](#gerritclusteringressconfig)
+   19. [GerritIngressTlsConfig](#gerritingresstlsconfig)
+   20. [GerritTemplate](#gerrittemplate)
+   21. [GerritTemplateSpec](#gerrittemplatespec)
+   22. [GerritProbe](#gerritprobe)
+   23. [GerritServiceConfig](#gerritserviceconfig)
+   24. [GerritSite](#gerritsite)
+   25. [GerritPlugin](#gerritplugin)
+   26. [GerritMode](#gerritmode)
+   27. [GerritSpec](#gerritspec)
+   28. [GerritStatus](#gerritstatus)
+   29. [IngressConfig](#ingressconfig)
+   30. [ReceiverTemplate](#receivertemplate)
+   31. [ReceiverTemplateSpec](#receivertemplatespec)
+   32. [ReceiverSpec](#receiverspec)
+   33. [ReceiverStatus](#receiverstatus)
+   34. [ReceiverProbe](#receiverprobe)
+   35. [ReceiverServiceConfig](#receiverserviceconfig)
+   36. [GitGarbageCollectionSpec](#gitgarbagecollectionspec)
+   37. [GitGarbageCollectionStatus](#gitgarbagecollectionstatus)
+   38. [GitGcState](#gitgcstate)
+   39. [GerritNetworkSpec](#gerritnetworkspec)
+   40. [NetworkMember](#networkmember)
+   41. [NetworkMemberWithSsh](#networkmemberwithssh)
 
 ## General Remarks
 
@@ -56,7 +57,7 @@
 ---
 
 **Group**: gerritoperator.google.com \
-**Version**: v1alpha4 \
+**Version**: v1alpha5 \
 **Kind**: GerritCluster
 
 ---
@@ -73,7 +74,7 @@
 Example:
 
 ```yaml
-apiVersion: "gerritoperator.google.com/v1alpha4"
+apiVersion: "gerritoperator.google.com/v1alpha5"
 kind: GerritCluster
 metadata:
   name: gerrit
@@ -113,6 +114,14 @@
           volume-type: ssd
           aws-availability-zone: us-east-1
 
+    sharedStorage:
+      size: 1Gi
+      volumeName: ""
+      selector:
+        matchLabels:
+          volume-type: ssd
+          aws-availability-zone: us-east-1
+
     logsStorage:
       size: 1Gi
       volumeName: ""
@@ -324,7 +333,7 @@
 ---
 
 **Group**: gerritoperator.google.com \
-**Version**: v1alpha5 \
+**Version**: v1alpha6 \
 **Kind**: Gerrit
 
 ---
@@ -341,7 +350,7 @@
 Example:
 
 ```yaml
-apiVersion: "gerritoperator.google.com/v1alpha5"
+apiVersion: "gerritoperator.google.com/v1alpha6"
 kind: Gerrit
 metadata:
   name: gerrit
@@ -491,6 +500,14 @@
           volume-type: ssd
           aws-availability-zone: us-east-1
 
+    sharedStorage:
+      size: 1Gi
+      volumeName: ""
+      selector:
+        matchLabels:
+          volume-type: ssd
+          aws-availability-zone: us-east-1
+
     logsStorage:
       size: 1Gi
       volumeName: ""
@@ -766,14 +783,22 @@
 |---|---|---|
 | `members` | `Map<String, List<String>>` | A map listing all Gerrit and Receiver instances managed by the GerritCluster by name |
 
+## StorageConfig
+
+| Field                  | Type                                              | Description                                                                                             |
+|------------------------|---------------------------------------------------|---------------------------------------------------------------------------------------------------------|
+| `storageClasses`       | [`StorageClassConfig`](#storageclassconfig)       | StorageClasses used in the GerritCluster                                                                |
+| `gitRepositoryStorage` | [`SharedStorage`](#sharedstorage)                 | Volume used for storing Git repositories                                                                |
+| `logsStorage`          | [`SharedStorage`](#sharedstorage)                 | Volume used for storing logs                                                                            |
+| `pluginCacheStorage`   | [`OptionalSharedStorage`](#optionalsharedstorage) | Volume used for caching downloaded plugin JAR-files (Only used by Gerrit resources. Otherwise ignored.) |
+
 ## GerritStorageConfig
 
+Extends [StorageConfig](#StorageConfig).
+
 | Field | Type | Description |
 |---|---|---|
-| `storageClasses` | [`StorageClassConfig`](#storageclassconfig) | StorageClasses used in the GerritCluster |
-| `gitRepositoryStorage` | [`SharedStorage`](#sharedstorage) | Volume used for storing Git repositories |
-| `logsStorage` | [`SharedStorage`](#sharedstorage) | Volume used for storing logs |
-| `pluginCacheStorage` | [`OptionalSharedStorage`](#optionalsharedstorage) | Volume used for caching downloaded plugin JAR-files (Only used by Gerrit resources. Otherwise ignored.) |
+| `sharedStorage` | [`SharedStorage`](#sharedstorage) | Volume used for resources shared between Gerrit instances except git repositories |
 
 ## StorageClassConfig
 
@@ -968,7 +993,7 @@
 
 | Field | Type | Description |
 |---|---|---|
-| `storage` | [`GerritStorageConfig`](#gerritstorageconfig) | Storage used by Gerrit/Receiver instances |
+| `storage` | [`StorageConfig`](#storageconfig) | Storage used by Gerrit/Receiver instances |
 | `containerImages` | [`ContainerImageConfig`](#containerimageconfig) | Container images used inside GerritCluster |
 | `ingress` | [`IngressConfig`](#ingressconfig) | Ingress configuration for Gerrit |
 
diff --git a/container-images/gerrit-init/tools/gerrit-initializer/initializer/tasks/init.py b/container-images/gerrit-init/tools/gerrit-initializer/initializer/tasks/init.py
index 8266f6f..7cf1646 100755
--- a/container-images/gerrit-init/tools/gerrit-initializer/initializer/tasks/init.py
+++ b/container-images/gerrit-init/tools/gerrit-initializer/initializer/tasks/init.py
@@ -122,6 +122,10 @@
         self._ensure_symlink(f"{MNT_PATH}/git", f"{self.site}/git")
         self._ensure_symlink(f"{MNT_PATH}/logs", f"{self.site}/logs")
 
+        mounted_shared_dir = f"{MNT_PATH}/shared"
+        if not self.is_replica and os.path.exists(mounted_shared_dir):
+            self._ensure_symlink(mounted_shared_dir, f"{self.site}/shared")
+
         index_type = self.gerrit_config.get("index.type", default=IndexType.LUCENE.name)
         if IndexType[index_type.upper()] is IndexType.ELASTICSEARCH:
             self._ensure_symlink(f"{MNT_PATH}/index", f"{self.site}/index")
diff --git a/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml b/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml
index 10fbbab..99e5161 100644
--- a/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml
+++ b/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml
@@ -13,7 +13,7 @@
     singular: gerritcluster
   scope: Namespaced
   versions:
-  - name: v1alpha4
+  - name: v1alpha5
     schema:
       openAPIV3Schema:
         properties:
@@ -21,6 +21,36 @@
             properties:
               storage:
                 properties:
+                  sharedStorage:
+                    properties:
+                      size:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        x-kubernetes-int-or-string: true
+                      volumeName:
+                        type: string
+                      selector:
+                        properties:
+                          matchExpressions:
+                            items:
+                              properties:
+                                key:
+                                  type: string
+                                operator:
+                                  type: string
+                                values:
+                                  items:
+                                    type: string
+                                  type: array
+                              type: object
+                            type: array
+                          matchLabels:
+                            additionalProperties:
+                              type: string
+                            type: object
+                        type: object
+                    type: object
                   storageClasses:
                     properties:
                       readWriteOnce:
diff --git a/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml b/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml
index 4991dd5..128a2eb 100644
--- a/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml
+++ b/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml
@@ -13,7 +13,7 @@
     singular: gerrit
   scope: Namespaced
   versions:
-  - name: v1alpha5
+  - name: v1alpha6
     schema:
       openAPIV3Schema:
         properties:
@@ -49,6 +49,36 @@
                 type: object
               storage:
                 properties:
+                  sharedStorage:
+                    properties:
+                      size:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        x-kubernetes-int-or-string: true
+                      volumeName:
+                        type: string
+                      selector:
+                        properties:
+                          matchExpressions:
+                            items:
+                              properties:
+                                key:
+                                  type: string
+                                operator:
+                                  type: string
+                                values:
+                                  items:
+                                    type: string
+                                  type: array
+                              type: object
+                            type: array
+                          matchLabels:
+                            additionalProperties:
+                              type: string
+                            type: object
+                        type: object
+                    type: object
                   storageClasses:
                     properties:
                       readWriteOnce:
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 712fdba..0877534 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
@@ -32,6 +32,8 @@
 import com.google.gerrit.k8s.operator.cluster.dependent.NfsWorkaroundCondition;
 import com.google.gerrit.k8s.operator.cluster.dependent.PluginCacheCondition;
 import com.google.gerrit.k8s.operator.cluster.dependent.PluginCachePVC;
+import com.google.gerrit.k8s.operator.cluster.dependent.SharedPVC;
+import com.google.gerrit.k8s.operator.cluster.dependent.SharedPVCCondition;
 import com.google.gerrit.k8s.operator.cluster.model.GerritCluster;
 import com.google.gerrit.k8s.operator.cluster.model.GerritClusterStatus;
 import com.google.gerrit.k8s.operator.gerrit.model.Gerrit;
@@ -65,6 +67,11 @@
           type = GitRepositoriesPVC.class,
           useEventSourceWithName = PVC_EVENT_SOURCE),
       @Dependent(
+          name = "shared-pvc",
+          type = SharedPVC.class,
+          reconcilePrecondition = SharedPVCCondition.class,
+          useEventSourceWithName = PVC_EVENT_SOURCE),
+      @Dependent(
           name = "gerrit-logs-pvc",
           type = GerritLogsPVC.class,
           useEventSourceWithName = PVC_EVENT_SOURCE),
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVC.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVC.java
new file mode 100644
index 0000000..c7a9e4f
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVC.java
@@ -0,0 +1,54 @@
+// 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.dependent;
+
+import com.google.gerrit.k8s.operator.cluster.model.GerritCluster;
+import com.google.gerrit.k8s.operator.shared.model.GerritStorageConfig;
+import com.google.gerrit.k8s.operator.shared.model.SharedStorage;
+import com.google.gerrit.k8s.operator.util.CRUDKubernetesDependentPVCResource;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
+import java.util.Map;
+
+@KubernetesDependent(resourceDiscriminator = SharedPVCDiscriminator.class)
+public class SharedPVC extends CRUDKubernetesDependentPVCResource<GerritCluster> {
+
+  public static final String SHARED_PVC_NAME = "shared-pvc";
+
+  @Override
+  protected PersistentVolumeClaim desiredPVC(
+      GerritCluster gerritCluster, Context<GerritCluster> context) {
+    GerritStorageConfig storageConfig = gerritCluster.getSpec().getStorage();
+    SharedStorage sharedStorage = storageConfig.getSharedStorage();
+    return new PersistentVolumeClaimBuilder()
+        .withNewMetadata()
+        .withName(SHARED_PVC_NAME)
+        .withNamespace(gerritCluster.getMetadata().getNamespace())
+        .withLabels(gerritCluster.getLabels("shared-storage", this.getClass().getSimpleName()))
+        .endMetadata()
+        .withNewSpec()
+        .withAccessModes("ReadWriteMany")
+        .withNewResources()
+        .withRequests(Map.of("storage", sharedStorage.getSize()))
+        .endResources()
+        .withStorageClassName(storageConfig.getStorageClasses().getReadWriteMany())
+        .withSelector(sharedStorage.getSelector())
+        .withVolumeName(sharedStorage.getVolumeName())
+        .endSpec()
+        .build();
+  }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVCCondition.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVCCondition.java
new file mode 100644
index 0000000..9b77db7
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVCCondition.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 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.dependent;
+
+import com.google.gerrit.k8s.operator.cluster.model.GerritCluster;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
+import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
+
+public class SharedPVCCondition implements Condition<PersistentVolumeClaim, GerritCluster> {
+
+  @Override
+  public boolean isMet(
+      DependentResource<PersistentVolumeClaim, GerritCluster> dependentResource,
+      GerritCluster gerritCluster,
+      Context<GerritCluster> context) {
+    return gerritCluster.getSpec().getGerrits().stream()
+        .anyMatch(g -> g.getSpec().isHighlyAvailablePrimary());
+  }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVCDiscriminator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVCDiscriminator.java
new file mode 100644
index 0000000..652f890
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/dependent/SharedPVCDiscriminator.java
@@ -0,0 +1,42 @@
+// 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.dependent;
+
+import static com.google.gerrit.k8s.operator.cluster.GerritClusterReconciler.PVC_EVENT_SOURCE;
+
+import com.google.gerrit.k8s.operator.cluster.model.GerritCluster;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
+import io.javaoperatorsdk.operator.processing.event.ResourceID;
+import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
+import java.util.Optional;
+
+public class SharedPVCDiscriminator
+    implements ResourceDiscriminator<PersistentVolumeClaim, GerritCluster> {
+  @Override
+  public Optional<PersistentVolumeClaim> distinguish(
+      Class<PersistentVolumeClaim> resource,
+      GerritCluster primary,
+      Context<GerritCluster> context) {
+    InformerEventSource<PersistentVolumeClaim, GerritCluster> ies =
+        (InformerEventSource<PersistentVolumeClaim, GerritCluster>)
+            context
+                .eventSourceRetriever()
+                .getResourceEventSourceFor(PersistentVolumeClaim.class, PVC_EVENT_SOURCE);
+
+    return ies.get(new ResourceID(SharedPVC.SHARED_PVC_NAME, primary.getMetadata().getNamespace()));
+  }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java
index 6091699..70dd705 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.k8s.operator.cluster.dependent.GerritLogsPVC.LOGS_PVC_NAME;
 import static com.google.gerrit.k8s.operator.cluster.dependent.GitRepositoriesPVC.REPOSITORY_PVC_NAME;
 import static com.google.gerrit.k8s.operator.cluster.dependent.NfsIdmapdConfigMap.NFS_IDMAPD_CM_NAME;
+import static com.google.gerrit.k8s.operator.cluster.dependent.SharedPVC.SHARED_PVC_NAME;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.gerrit.k8s.operator.cluster.GerritClusterMemberSpec;
@@ -42,11 +43,12 @@
 import org.apache.commons.lang3.builder.ToStringStyle;
 
 @Group("gerritoperator.google.com")
-@Version("v1alpha4")
+@Version("v1alpha5")
 @ShortNames("gclus")
 public class GerritCluster extends CustomResource<GerritClusterSpec, GerritClusterStatus>
     implements Namespaced {
   private static final long serialVersionUID = 2L;
+  private static final String SHARED_VOLUME_NAME = "shared";
   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";
@@ -102,6 +104,16 @@
   }
 
   @JsonIgnore
+  public static Volume getSharedVolume() {
+    return new VolumeBuilder()
+        .withName(SHARED_VOLUME_NAME)
+        .withNewPersistentVolumeClaim()
+        .withClaimName(SHARED_PVC_NAME)
+        .endPersistentVolumeClaim()
+        .build();
+  }
+
+  @JsonIgnore
   public static Volume getGitRepositoriesVolume() {
     return new VolumeBuilder()
         .withName(GIT_REPOSITORIES_VOLUME_NAME)
@@ -125,6 +137,16 @@
   }
 
   @JsonIgnore
+  public static VolumeMount getSharedVolumeMount() {
+    return getSharedVolumeMount("/var/mnt/shared");
+  }
+
+  @JsonIgnore
+  public static VolumeMount getSharedVolumeMount(String mountPath) {
+    return new VolumeMountBuilder().withName(SHARED_VOLUME_NAME).withMountPath(mountPath).build();
+  }
+
+  @JsonIgnore
   public static Volume getLogsVolume() {
     return new VolumeBuilder()
         .withName(LOGS_VOLUME_NAME)
@@ -183,22 +205,41 @@
   @JsonIgnore
   public static Container createNfsInitContainer(
       boolean configureIdmapd, ContainerImageConfig imageConfig) {
+    return createNfsInitContainer(configureIdmapd, imageConfig, List.of());
+  }
+
+  @JsonIgnore
+  public static Container createNfsInitContainer(
+      boolean configureIdmapd,
+      ContainerImageConfig imageConfig,
+      List<VolumeMount> additionalVolumeMounts) {
     List<VolumeMount> volumeMounts = new ArrayList<>();
     volumeMounts.add(getLogsVolumeMount());
     volumeMounts.add(getGitRepositoriesVolumeMount());
 
+    volumeMounts.addAll(additionalVolumeMounts);
+
     if (configureIdmapd) {
       volumeMounts.add(getNfsImapdConfigVolumeMount());
     }
 
+    StringBuilder args = new StringBuilder();
+    args.append("chown -R ");
+    args.append(GERRIT_FS_UID);
+    args.append(":");
+    args.append(GERRIT_FS_GID);
+    args.append(" ");
+    for (VolumeMount vm : volumeMounts) {
+      args.append(vm.getMountPath());
+      args.append(" ");
+    }
+
     return new ContainerBuilder()
         .withName("nfs-init")
         .withImagePullPolicy(imageConfig.getImagePullPolicy())
         .withImage(imageConfig.getBusyBox().getBusyBoxImage())
         .withCommand(List.of("sh", "-c"))
-        .withArgs(
-            String.format(
-                "chown -R %d:%d /var/mnt/logs /var/mnt/git", GERRIT_FS_UID, GERRIT_FS_GID))
+        .withArgs(args.toString().trim())
         .withEnv(getPodNameEnvVar())
         .withVolumeMounts(volumeMounts)
         .build();
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java
index 818f7ad..a4afbec 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.k8s.operator.cluster.model.GerritCluster;
 import com.google.gerrit.k8s.operator.gerrit.GerritReconciler;
 import com.google.gerrit.k8s.operator.gerrit.model.Gerrit;
+import com.google.gerrit.k8s.operator.shared.model.ContainerImageConfig;
 import com.google.gerrit.k8s.operator.shared.model.NfsWorkaroundConfig;
 import io.fabric8.kubernetes.api.model.Container;
 import io.fabric8.kubernetes.api.model.ContainerPort;
@@ -66,11 +67,19 @@
     NfsWorkaroundConfig nfsWorkaround =
         gerrit.getSpec().getStorage().getStorageClasses().getNfsWorkaround();
     if (nfsWorkaround.isEnabled() && nfsWorkaround.isChownOnStartup()) {
-      initContainers.add(
-          GerritCluster.createNfsInitContainer(
-              gerrit.getSpec().getStorage().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
-                  != null,
-              gerrit.getSpec().getContainerImages()));
+      boolean hasIdmapdConfig =
+          gerrit.getSpec().getStorage().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
+              != null;
+      ContainerImageConfig images = gerrit.getSpec().getContainerImages();
+
+      if (gerrit.getSpec().isHighlyAvailablePrimary()) {
+
+        initContainers.add(
+            GerritCluster.createNfsInitContainer(
+                hasIdmapdConfig, images, List.of(GerritCluster.getSharedVolumeMount())));
+      } else {
+        initContainers.add(GerritCluster.createNfsInitContainer(hasIdmapdConfig, images));
+      }
     }
 
     Map<String, String> replicaSetAnnotations = new HashMap<>();
@@ -194,6 +203,9 @@
   private Set<Volume> getVolumes(Gerrit gerrit) {
     Set<Volume> volumes = new HashSet<>();
 
+    if (gerrit.getSpec().isHighlyAvailablePrimary()) {
+      volumes.add(GerritCluster.getSharedVolume());
+    }
     volumes.add(GerritCluster.getGitRepositoriesVolume());
     volumes.add(GerritCluster.getLogsVolume());
 
@@ -245,6 +257,9 @@
     Set<VolumeMount> volumeMounts = new HashSet<>();
     volumeMounts.add(
         new VolumeMountBuilder().withName(SITE_VOLUME_NAME).withMountPath("/var/gerrit").build());
+    if (gerrit.getSpec().isHighlyAvailablePrimary()) {
+      volumeMounts.add(GerritCluster.getSharedVolumeMount());
+    }
     volumeMounts.add(GerritCluster.getGitRepositoriesVolumeMount());
     volumeMounts.add(GerritCluster.getLogsVolumeMount());
     volumeMounts.add(
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java
index bfe38ee..0064a4c 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java
@@ -24,7 +24,7 @@
 import org.apache.commons.lang3.builder.ToStringStyle;
 
 @Group("gerritoperator.google.com")
-@Version("v1alpha5")
+@Version("v1alpha6")
 @ShortNames("gcr")
 public class Gerrit extends CustomResource<GerritSpec, GerritStatus> implements Namespaced {
   private static final long serialVersionUID = 2L;
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java
index fe88fb8..7f8fa92 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.k8s.operator.gerrit.model;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.gerrit.k8s.operator.gerrit.model.GerritTemplateSpec.GerritMode;
 import com.google.gerrit.k8s.operator.shared.model.HttpSshServiceConfig;
 import io.fabric8.kubernetes.api.model.Affinity;
 import io.fabric8.kubernetes.api.model.ResourceRequirements;
@@ -217,4 +219,11 @@
     PRIMARY,
     REPLICA
   }
+
+  @JsonIgnore
+  public boolean isHighlyAvailablePrimary() {
+    return getPlugins().stream().anyMatch(p -> p.getName().equals("high-availability"))
+        && getMode().equals(GerritMode.PRIMARY)
+        && getReplicas() > 1;
+  }
 }
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverSpec.java b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverSpec.java
index bd2d8f3..96df251 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverSpec.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverSpec.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.k8s.operator.receiver.model;
 
 import com.google.gerrit.k8s.operator.shared.model.ContainerImageConfig;
-import com.google.gerrit.k8s.operator.shared.model.GerritStorageConfig;
 import com.google.gerrit.k8s.operator.shared.model.IngressConfig;
+import com.google.gerrit.k8s.operator.shared.model.StorageConfig;
 
 public class ReceiverSpec extends ReceiverTemplateSpec {
   private ContainerImageConfig containerImages = new ContainerImageConfig();
-  private GerritStorageConfig storage = new GerritStorageConfig();
+  private StorageConfig storage = new StorageConfig();
   private IngressConfig ingress = new IngressConfig();
 
   public ReceiverSpec() {}
@@ -37,11 +37,11 @@
     this.containerImages = containerImages;
   }
 
-  public GerritStorageConfig getStorage() {
+  public StorageConfig getStorage() {
     return storage;
   }
 
-  public void setStorage(GerritStorageConfig storage) {
+  public void setStorage(StorageConfig storage) {
     this.storage = storage;
   }
 
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java
index e76f5eb..5c4332a 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/model/ReceiverTemplate.java
@@ -21,6 +21,7 @@
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.google.gerrit.k8s.operator.cluster.model.GerritCluster;
 import com.google.gerrit.k8s.operator.shared.model.IngressConfig;
+import com.google.gerrit.k8s.operator.shared.model.StorageConfig;
 import io.fabric8.kubernetes.api.model.KubernetesResource;
 import io.fabric8.kubernetes.api.model.ObjectMeta;
 import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
@@ -72,7 +73,7 @@
     receiver.setMetadata(getReceiverMetadata(gerritCluster));
     ReceiverSpec receiverSpec = new ReceiverSpec(spec);
     receiverSpec.setContainerImages(gerritCluster.getSpec().getContainerImages());
-    receiverSpec.setStorage(gerritCluster.getSpec().getStorage());
+    receiverSpec.setStorage(new StorageConfig(gerritCluster.getSpec().getStorage()));
     IngressConfig ingressConfig = new IngressConfig();
     ingressConfig.setHost(gerritCluster.getSpec().getIngress().getHost());
     ingressConfig.setTlsEnabled(gerritCluster.getSpec().getIngress().getTls().isEnabled());
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/GerritStorageConfig.java b/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/GerritStorageConfig.java
index d90480c..3fc558d 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/GerritStorageConfig.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/GerritStorageConfig.java
@@ -14,42 +14,14 @@
 
 package com.google.gerrit.k8s.operator.shared.model;
 
-public class GerritStorageConfig {
+public class GerritStorageConfig extends StorageConfig {
+  private SharedStorage sharedStorage;
 
-  private StorageClassConfig storageClasses;
-  private SharedStorage gitRepositoryStorage;
-  private SharedStorage logsStorage;
-  private OptionalSharedStorage pluginCacheStorage = new OptionalSharedStorage();
-
-  public StorageClassConfig getStorageClasses() {
-    return storageClasses;
+  public SharedStorage getSharedStorage() {
+    return sharedStorage;
   }
 
-  public SharedStorage getGitRepositoryStorage() {
-    return gitRepositoryStorage;
-  }
-
-  public void setStorageClasses(StorageClassConfig storageClasses) {
-    this.storageClasses = storageClasses;
-  }
-
-  public void setGitRepositoryStorage(SharedStorage gitRepositoryStorage) {
-    this.gitRepositoryStorage = gitRepositoryStorage;
-  }
-
-  public SharedStorage getLogsStorage() {
-    return logsStorage;
-  }
-
-  public void setLogsStorage(SharedStorage logsStorage) {
-    this.logsStorage = logsStorage;
-  }
-
-  public OptionalSharedStorage getPluginCacheStorage() {
-    return pluginCacheStorage;
-  }
-
-  public void setPluginCacheStorage(OptionalSharedStorage pluginCacheStorage) {
-    this.pluginCacheStorage = pluginCacheStorage;
+  public void setSharedStorage(SharedStorage sharedStorage) {
+    this.sharedStorage = sharedStorage;
   }
 }
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/StorageConfig.java b/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/StorageConfig.java
new file mode 100644
index 0000000..d7d0eb7
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/shared/model/StorageConfig.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 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.shared.model;
+
+public class StorageConfig {
+
+  private StorageClassConfig storageClasses;
+  private SharedStorage gitRepositoryStorage;
+  private SharedStorage logsStorage;
+  private OptionalSharedStorage pluginCacheStorage = new OptionalSharedStorage();
+
+  public StorageConfig() {}
+
+  public StorageConfig(GerritStorageConfig gerritStorageConfig) {
+    storageClasses = gerritStorageConfig.getStorageClasses();
+    gitRepositoryStorage = gerritStorageConfig.getGitRepositoryStorage();
+    logsStorage = gerritStorageConfig.getLogsStorage();
+    pluginCacheStorage = gerritStorageConfig.getPluginCacheStorage();
+  }
+
+  public StorageClassConfig getStorageClasses() {
+    return storageClasses;
+  }
+
+  public void setStorageClasses(StorageClassConfig storageClasses) {
+    this.storageClasses = storageClasses;
+  }
+
+  public SharedStorage getGitRepositoryStorage() {
+    return gitRepositoryStorage;
+  }
+
+  public void setGitRepositoryStorage(SharedStorage gitRepositoryStorage) {
+    this.gitRepositoryStorage = gitRepositoryStorage;
+  }
+
+  public SharedStorage getLogsStorage() {
+    return logsStorage;
+  }
+
+  public void setLogsStorage(SharedStorage logsStorage) {
+    this.logsStorage = logsStorage;
+  }
+
+  public OptionalSharedStorage getPluginCacheStorage() {
+    return pluginCacheStorage;
+  }
+
+  public void setPluginCacheStorage(OptionalSharedStorage pluginCacheStorage) {
+    this.pluginCacheStorage = pluginCacheStorage;
+  }
+}