app/gerrit/kube: implement

This change impelements the k8s machinery for Gerrit.

This might look somewhat complex at first, but the gist of it is:

 - k8s mounts etc, git, cache, db, index as RW PVs
 - k8s mounts a configmap containing gerrit.conf into an external
   directory
 - k8s mounts a secret containing secure.conf into an external directory
 - on startup, gerrit's entrypoint will copy over {gerrit,secure}.conf
   and start a small updater script that copies over gerrit.conf if
   there's any change. This should, in theory, make gerrit reload its
   config.

This is already running on production. You're probably looking at this
change through the instance deployed by itself :)

Change-Id: Ida9dff721c17cf4da7fb6ccbb54d2c4024672572
diff --git a/app/gerrit/kube/gerrit.libsonnet b/app/gerrit/kube/gerrit.libsonnet
new file mode 100644
index 0000000..2cd6f59
--- /dev/null
+++ b/app/gerrit/kube/gerrit.libsonnet
@@ -0,0 +1,209 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+    local gerrit = self,
+    local cfg = gerrit.cfg,
+
+    cfg:: {
+        namespace: error "namespace must be set",
+        appName: "gerrit",
+        prefix: "", # if set, should be 'foo-'
+        domain: error "domain must be set",
+        identity: error "identity (UUID) must be set",
+
+        // The secret must contain a key named 'secure.config' containing (at least):
+        // [auth]
+        //      registerEmailPrivateKey = <random>
+        // [plugin "gerrit-oauth-provider-warsawhackerspace-oauth"]
+        //      client-id = foo
+        //      client-secret = bar
+        // [sendemail]
+        //      smtpPass = foo
+        // [receiveemail]
+        //      password = bar
+        secureSecret: error "secure secret name must be set",
+
+        storageClass: error "storage class must be set",
+        storageSize: {
+            git: "50Gi", // Main storage for repositories and NoteDB.
+            index: "10Gi", // Secondary Lucene index
+            cache: "10Gi", // H2 cache databases
+            db: "1Gi", // NoteDB is used, so database is basically empty (H2 accountPatchReviewDatabase)
+            etc: "1Gi", // Random site stuff.
+        },
+
+        email: {
+            server: "mail.hackerspace.pl",
+            username: "gerrit",
+            address: "gerrit@hackerspace.pl",
+        },
+
+        tag: "3.0.0-r7",
+        image: "registry.k0.hswaw.net/app/gerrit:" + cfg.tag,
+        resources: {
+            requests: {
+                cpu: "100m",
+                memory: "500Mi",
+            },
+            limits: {
+                cpu: "1",
+                memory: "2Gi",
+            },
+        },
+    },
+
+    name(suffix):: cfg.prefix + suffix,
+
+    metadata(component):: {
+        namespace: cfg.namespace,
+        labels: {
+            "app.kubernetes.io/name": cfg.appName,
+            "app.kubernetes.io/managed-by": "kubecfg",
+            "app.kubernetes.io/component": "component",
+        },
+    },
+
+    configmap: kube.ConfigMap(gerrit.name("gerrit")) {
+        metadata+: gerrit.metadata("configmap"),
+        data: {
+            "gerrit.config": |||
+                [gerrit]
+                    basePath  = git
+                    canonicalWebUrl = https://%(domain)s/
+                    serverId = %(identity)s
+
+                [container]
+                    javaOptions = -Djava.security.edg=file:/dev/./urandom
+
+                [auth]
+                    type = OAUTH
+                    gitBasicAuthPolicy = HTTP
+
+                [httpd]
+                    listenUrl = proxy-http://*:8080
+
+                [sshd]
+                    advertisedAddress = %(domain)s
+
+                [user]
+                    email = %(emailAddress)s
+
+                [sendemail]
+                    enable = true
+                    from = MIXED
+                    smtpServer = %(emailServer)s
+                    smtpServerPort = 465
+                    smtpEncryption = ssl
+                    smtpUser = %(emailUser)s
+
+                [receiveemail]
+                    protocol = IMAP
+                    host = %(emailServer)s
+                    username = %(emailUser)s
+                    encryption = TLS
+                    enableImapIdle = true
+
+            ||| % {
+                domain: cfg.domain,
+                identity: cfg.identity,
+                emailAddress: cfg.email.address,
+                emailServer: cfg.email.server,
+                emailUser: cfg.email.username,
+            },
+        },
+    },
+
+    volumes: {
+        [name]: kube.PersistentVolumeClaim(gerrit.name(name)) {
+            metadata+: gerrit.metadata("storage"),
+            spec+: {
+                storageClassName: cfg.storageClassName,
+                accessModes: ["ReadWriteOnce"],
+                resources: {
+                    requests: {
+                        storage: cfg.storageSize[name],
+                    },
+                },
+            },
+        }
+        for name in ["etc", "git", "index", "cache", "db"]
+    },
+
+    local volumeMounts = {
+        [name]: { mountPath: "/var/gerrit/%s" % name }
+        for name in ["etc", "git", "index", "cache", "db"]
+    } {
+        // ConfigMap gets mounted here
+        config: { mountPath: "/var/gerrit-config" },
+        // SecureSecret gets mounted here
+        secure: { mountPath: "/var/gerrit-secure" },
+    },
+    deployment: kube.Deployment(gerrit.name("gerrit")) {
+        metadata+: gerrit.metadata("deployment"),
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    securityContext: {
+                        fsGroup: 1000, # gerrit uid
+                    },
+                    volumes_: {
+                        config: kube.ConfigMapVolume(gerrit.configmap),
+                        secure: { secret: { secretName: cfg.secureSecret} },
+                    } {
+                        [name]: kube.PersistentVolumeClaimVolume(gerrit.volumes[name])
+                        for name in ["etc", "git", "index", "cache", "db"]
+                    },
+                    containers_: {
+                        gerrit: kube.Container(gerrit.name("gerrit")) {
+                            image: cfg.image,
+                            ports_: {
+                                http: { containerPort: 8080 },
+                                ssh: { containerPort: 29418 },
+                            },
+                            resources: cfg.resources,
+                            volumeMounts_: volumeMounts,
+                        },
+                    },
+                },
+            },
+        },
+    },
+
+    svc: kube.Service(gerrit.name("gerrit")) {
+        metadata+: gerrit.metadata("service"),
+        target_pod:: gerrit.deployment.spec.template,
+        spec+: {
+            ports: [
+                { name: "http", port: 80, targetPort: 8080, protocol: "TCP" },
+                { name: "ssh", port: 22, targetPort: 29418, protocol: "TCP" },
+            ],
+            type: "ClusterIP",
+        },
+    },
+
+    ingress: kube.Ingress(gerrit.name("gerrit")) {
+        metadata+: gerrit.metadata("ingress") {
+            annotations+: {
+                "kubernetes.io/tls-acme": "true",
+                "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+                "nginx.ingress.kubernetes.io/proxy-body-size": "0",
+            },
+        },
+        spec+: {
+            tls: [
+                { hosts: [cfg.domain], secretName: gerrit.name("acme") },
+            ],
+            rules: [
+                {
+                    host: cfg.domain, 
+                    http: {
+                        paths: [
+                            { path: "/", backend: gerrit.svc.name_port },
+                        ],
+                    },
+                }
+            ],
+        },
+    },
+}