[Operator] Allow to configure to chown NFS volumes

Change-Id: I573ce6a9d5eed385d4bd68a28d2b5848e47b8074
diff --git a/operator/README.md b/operator/README.md
index a4e2e02..6ea2313 100644
--- a/operator/README.md
+++ b/operator/README.md
@@ -147,10 +147,15 @@
     ## 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. (default: false)
+      ## If enabled, below options might be used. (default: false)
       enabled: false
 
+      ## If enabled, the ownership of the mounted NFS volumes will be set on pod
+      ## startup. Note that this is not done recursively. It is expected that all
+      ## data already present in the volume was created by the user used in accessing
+      ## containers. (default: false)
+      chownOnStartup: 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. (optional)
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 ddc15ac..98f6232 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
@@ -20,6 +20,10 @@
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.gerrit.k8s.operator.gerrit.Gerrit;
+import io.fabric8.kubernetes.api.model.Container;
+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.Namespaced;
 import io.fabric8.kubernetes.api.model.Volume;
 import io.fabric8.kubernetes.api.model.VolumeBuilder;
@@ -29,7 +33,9 @@
 import io.fabric8.kubernetes.model.annotation.Group;
 import io.fabric8.kubernetes.model.annotation.ShortNames;
 import io.fabric8.kubernetes.model.annotation.Version;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
@@ -43,6 +49,8 @@
   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";
+  private static final int GERRIT_FS_UID = 1000;
+  private static final int GERRIT_FS_GID = 100;
 
   public String toString() {
     return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
@@ -129,4 +137,39 @@
   public static boolean isGerritInstancePartOfCluster(Gerrit gerrit, GerritCluster cluster) {
     return gerrit.getSpec().getCluster().equals(cluster.getMetadata().getName());
   }
+
+  @JsonIgnore
+  public Container createNfsInitContainer() {
+    List<VolumeMount> volumeMounts = new ArrayList<>();
+    volumeMounts.add(getLogsVolumeMount());
+    volumeMounts.add(getGitRepositoriesVolumeMount());
+
+    if (getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig() != null) {
+      volumeMounts.add(getNfsImapdConfigVolumeMount());
+    }
+
+    return new ContainerBuilder()
+        .withName("nfs-init")
+        .withImagePullPolicy(getSpec().getImagePullPolicy())
+        .withImage(getSpec().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))
+        .withEnv(getPodNameEnvVar())
+        .withVolumeMounts(volumeMounts)
+        .build();
+  }
+
+  @JsonIgnore
+  public static EnvVar getPodNameEnvVar() {
+    return new EnvVarBuilder()
+        .withName("POD_NAME")
+        .withNewValueFrom()
+        .withNewFieldRef()
+        .withFieldPath("metadata.name")
+        .endFieldRef()
+        .endValueFrom()
+        .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
index 5408333..590a1b0 100644
--- 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
@@ -17,6 +17,7 @@
 public class NfsWorkaroundConfig {
 
   private boolean enabled = false;
+  private boolean chownOnStartup = false;
   private String idmapdConfig;
 
   public boolean isEnabled() {
@@ -27,6 +28,14 @@
     this.enabled = enabled;
   }
 
+  public boolean isChownOnStartup() {
+    return chownOnStartup;
+  }
+
+  public void setChownOnStartup(boolean chownOnStartup) {
+    this.chownOnStartup = chownOnStartup;
+  }
+
   public String getIdmapdConfig() {
     return idmapdConfig;
   }
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/StatefulSetDependentResource.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/StatefulSetDependentResource.java
index 7726274..ece807b 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/StatefulSetDependentResource.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/StatefulSetDependentResource.java
@@ -15,7 +15,9 @@
 package com.google.gerrit.k8s.operator.gerrit;
 
 import com.google.gerrit.k8s.operator.cluster.GerritCluster;
+import com.google.gerrit.k8s.operator.cluster.NfsWorkaroundConfig;
 import com.google.gerrit.k8s.operator.cluster.PluginCachePVC;
+import io.fabric8.kubernetes.api.model.Container;
 import io.fabric8.kubernetes.api.model.ContainerPort;
 import io.fabric8.kubernetes.api.model.Volume;
 import io.fabric8.kubernetes.api.model.VolumeBuilder;
@@ -59,6 +61,14 @@
 
     StatefulSetBuilder stsBuilder = new StatefulSetBuilder();
 
+    List<Container> initContainers = new ArrayList<>();
+
+    NfsWorkaroundConfig nfsWorkaround =
+        gerritCluster.getSpec().getStorageClasses().getNfsWorkaround();
+    if (nfsWorkaround.isEnabled() && nfsWorkaround.isChownOnStartup()) {
+      initContainers.add(gerritCluster.createNfsInitContainer());
+    }
+
     stsBuilder
         .withApiVersion("apps/v1")
         .withNewMetadata()
@@ -98,6 +108,7 @@
         .withResources(gerrit.getSpec().getResources())
         .addAllToVolumeMounts(getVolumeMounts(gerrit, gerritCluster, true))
         .endInitContainer()
+        .addAllToInitContainers(initContainers)
         .addNewContainer()
         .withName("gerrit")
         .withImagePullPolicy(gerritCluster.getSpec().getImagePullPolicy())
@@ -110,6 +121,7 @@
         .endExec()
         .endPreStop()
         .endLifecycle()
+        .withEnv(GerritCluster.getPodNameEnvVar())
         .withPorts(getContainerPorts(gerrit))
         .withResources(gerrit.getSpec().getResources())
         .withStartupProbe(gerrit.getSpec().getStartupProbe())
@@ -186,6 +198,12 @@
               .build());
     }
 
+    NfsWorkaroundConfig nfsWorkaround =
+        gerritCluster.getSpec().getStorageClasses().getNfsWorkaround();
+    if (nfsWorkaround.isEnabled() && nfsWorkaround.getIdmapdConfig() != null) {
+      volumes.add(gerritCluster.getNfsImapdConfigVolume());
+    }
+
     return volumes;
   }
 
@@ -225,6 +243,13 @@
                 .build());
       }
     }
+
+    NfsWorkaroundConfig nfsWorkaround =
+        gerritCluster.getSpec().getStorageClasses().getNfsWorkaround();
+    if (nfsWorkaround.isEnabled() && nfsWorkaround.getIdmapdConfig() != null) {
+      volumeMounts.add(gerritCluster.getNfsImapdConfigVolumeMount());
+    }
+
     return volumeMounts;
   }
 
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 4c2e5e4..83e1874 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
@@ -18,8 +18,6 @@
 import com.google.gerrit.k8s.operator.cluster.GerritCluster;
 import io.fabric8.kubernetes.api.model.Container;
 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;
@@ -65,7 +63,9 @@
         List.of(gerritCluster.getGitRepositoriesVolume(), gerritCluster.getLogsVolume());
 
     if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().isEnabled()) {
-      initContainers.add(createNfsImapdInitContainer(gerritCluster));
+      if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().isChownOnStartup()) {
+        initContainers.add(gerritCluster.createNfsInitContainer());
+      }
       if (gerritCluster.getSpec().getStorageClasses().getNfsWorkaround().getIdmapdConfig()
           != null) {
         volumes.add(gerritCluster.getNfsImapdConfigVolume());
@@ -123,37 +123,6 @@
         .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) {
     List<VolumeMount> volumeMounts =
         List.of(
@@ -172,7 +141,7 @@
             .withImagePullPolicy(gerritCluster.getSpec().getImagePullPolicy())
             .withImage(gerritCluster.getSpec().getGerritImages().getFullImageName("git-gc"))
             .withResources(gitGc.getSpec().getResources())
-            .withEnv(getPodNameEnvVar())
+            .withEnv(GerritCluster.getPodNameEnvVar())
             .withVolumeMounts(volumeMounts);
 
     ArrayList<String> args = new ArrayList<>();